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

  FileName    [wlcUif.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Verilog parser.]

  Synopsis    [Abstraction for word-level networks.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - August 22, 2014.]

  Revision    [$Id: wlcUif.c,v 1.00 2014/09/12 00:00:00 alanmi Exp $]

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

#include "wlc.h"

ABC_NAMESPACE_IMPL_START

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

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

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

  Synopsis    [Check if two objects have the same input/output signatures.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Wlc_NtkPairIsUifable( Wlc_Ntk_t * p, Wlc_Obj_t * pObj, Wlc_Obj_t * pObj2 )
{
    Wlc_Obj_t * pFanin, * pFanin2;  int k;
    if ( Wlc_ObjRange(pObj) != Wlc_ObjRange(pObj2) )
        return 0;
    if ( Wlc_ObjIsSigned(pObj) != Wlc_ObjIsSigned(pObj2) )
        return 0;
    if ( Wlc_ObjFaninNum(pObj) != Wlc_ObjFaninNum(pObj2) )
        return 0;
    for ( k = 0; k < Wlc_ObjFaninNum(pObj); k++ )
    {
        pFanin = Wlc_ObjFanin(p, pObj, k);
        pFanin2 = Wlc_ObjFanin(p, pObj2, k);
        if ( Wlc_ObjRange(pFanin) != Wlc_ObjRange(pFanin2) )
            return 0;
        if ( Wlc_ObjIsSigned(pFanin) != Wlc_ObjIsSigned(pFanin2) )
            return 0;
    }
    return 1;
}

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

  Synopsis    [Collect IDs of the multipliers.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Int_t * Wlc_NtkCollectMultipliers( Wlc_Ntk_t * p )
{
    Wlc_Obj_t * pObj;  int i;
    Vec_Int_t * vBoxIds = Vec_IntAlloc( 100 );
    Wlc_NtkForEachObj( p, pObj, i )
        if ( pObj->Type == WLC_OBJ_ARI_MULTI )
            Vec_IntPush( vBoxIds, i );
    if ( Vec_IntSize( vBoxIds ) > 0 )
        return vBoxIds;
    Vec_IntFree( vBoxIds );
    return NULL;
}

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

  Synopsis    [Returns all pairs of uifable multipliers.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Int_t * Wlc_NtkFindUifableMultiplierPairs( Wlc_Ntk_t * p )
{
    Vec_Int_t * vMultis = Wlc_NtkCollectMultipliers( p );
    Vec_Int_t * vPairs = Vec_IntAlloc( 2 );
    Wlc_Obj_t * pObj, * pObj2;  int i, k;
    // iterate through unique pairs
    Wlc_NtkForEachObjVec( vMultis, p, pObj, i )
        Wlc_NtkForEachObjVec( vMultis, p, pObj2, k )
        {
            if ( k == i )  
                break;
            if ( Wlc_NtkPairIsUifable( p, pObj, pObj2 ) )
            {
                Vec_IntPush( vPairs, Wlc_ObjId(p, pObj) );
                Vec_IntPush( vPairs, Wlc_ObjId(p, pObj2) );
            }
        }
    Vec_IntFree( vMultis );
    if ( Vec_IntSize( vPairs ) > 0 )
        return vPairs;
    Vec_IntFree( vPairs );
    return NULL;
}



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

  Synopsis    [Abstracts nodes by replacing their outputs with new PIs.]

  Description [If array is NULL, abstract all multipliers.]
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Wlc_Ntk_t * Wlc_NtkAbstractNodes( Wlc_Ntk_t * p, Vec_Int_t * vNodesInit )
{
    Vec_Int_t * vNodes = vNodesInit;
    Wlc_Ntk_t * pNew;
    Wlc_Obj_t * pObj;
    int i, k, iObj, iFanin;
    // get multipliers if not given
    if ( vNodes == NULL )
        vNodes = Wlc_NtkCollectMultipliers( p );
    if ( vNodes == NULL )
        return NULL;
    // mark nodes
    Wlc_NtkForEachObjVec( vNodes, p, pObj, i )
        pObj->Mark = 1;
    // iterate through the nodes in the DFS order
    Wlc_NtkCleanCopy( p );
    Wlc_NtkForEachObj( p, pObj, i )
    {
        if ( i == Vec_IntSize(&p->vCopies) )
            break;
        if ( pObj->Mark ) {
            // clean
            pObj->Mark = 0;
            // add fresh PI with the same number of bits
            iObj = Wlc_ObjAlloc( p, WLC_OBJ_PI, Wlc_ObjIsSigned(pObj), Wlc_ObjRange(pObj) - 1, 0 );
        }
        else {
            // update fanins 
            Wlc_ObjForEachFanin( pObj, iFanin, k )
                Wlc_ObjFanins(pObj)[k] = Wlc_ObjCopy(p, iFanin);
            // node to remain
            iObj = i;
        }
        Wlc_ObjSetCopy( p, i, iObj );
    }
    // POs do not change in this procedure
    if ( vNodes != vNodesInit )
        Vec_IntFree( vNodes );
    // reconstruct topological order
    pNew = Wlc_NtkDupDfs( p, 0, 1 );
    return pNew;
}

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

  Synopsis    [Adds UIF constraints to node pairs and updates POs.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Wlc_Ntk_t * Wlc_NtkUifNodePairs( Wlc_Ntk_t * p, Vec_Int_t * vPairsInit )
{
    Vec_Int_t * vPairs = vPairsInit;
    Wlc_Ntk_t * pNew;
    Wlc_Obj_t * pObj, * pObj2;
    Vec_Int_t * vUifConstrs, * vCompares, * vFanins;
    int i, k, iObj, iObj2, iObjNew, iObjNew2;
    int iFanin, iFanin2, iFaninNew;
    // get multiplier pairs if not given
    if ( vPairs == NULL )
        vPairs = Wlc_NtkFindUifableMultiplierPairs( p );
    if ( vPairs == NULL )
        return NULL;
    // sanity checks
    assert( Vec_IntSize(vPairs) > 0 && Vec_IntSize(vPairs) % 2 == 0 );
    // iterate through node pairs
    vFanins = Vec_IntAlloc( 100 );
    vCompares = Vec_IntAlloc( 100 );
    vUifConstrs = Vec_IntAlloc( 100 );
    Vec_IntForEachEntryDouble( vPairs, iObj, iObj2, i )
    {
        // get two nodes
        pObj  = Wlc_NtkObj( p, iObj );
        pObj2 = Wlc_NtkObj( p, iObj2 );
        assert( Wlc_NtkPairIsUifable(p, pObj, pObj2) );
        // create fanin comparator nodes
        Vec_IntClear( vCompares );
        Wlc_ObjForEachFanin( pObj, iFanin, k )
        {
            iFanin2 = Wlc_ObjFaninId( pObj2, k );
            Vec_IntFillTwo( vFanins, 2, iFanin, iFanin2 );
            iFaninNew = Wlc_ObjCreate( p, WLC_OBJ_COMP_NOTEQU, 0, 0, 0, vFanins );
            Vec_IntPush( vCompares, iFaninNew );
            // note that a pointer to Wlc_Obj_t (for example, pObj) can be invalidated after a call to 
            // Wlc_ObjCreate() due to a possible realloc of the internal array of objects...
            pObj = Wlc_NtkObj( p, iObj );
        }
        // concatenate fanin comparators
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_BIT_CONCAT, 0, Vec_IntSize(vCompares) - 1, 0, vCompares );
        // create reduction-OR node
        Vec_IntFill( vFanins, 1, iObjNew );
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_REDUCT_OR, 0, 0, 0, vFanins );
        // craete output comparator node
        Vec_IntFillTwo( vFanins, 2, iObj, iObj2 );
        iObjNew2 = Wlc_ObjCreate( p, WLC_OBJ_COMP_EQU, 0, 0, 0, vFanins );
        // create implication node (iObjNew is already complemented above)
        Vec_IntFillTwo( vFanins, 2, iObjNew, iObjNew2 );
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_LOGIC_OR, 0, 0, 0, vFanins );
        // save the constraint
        Vec_IntPush( vUifConstrs, iObjNew );
    }
    // derive the AND of the UIF contraints
    assert( Vec_IntSize(vUifConstrs) > 0 );
    if ( Vec_IntSize(vUifConstrs) == 1 )
        iObjNew = Vec_IntEntry( vUifConstrs, 0 );
    else
    {
        // concatenate
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_BIT_CONCAT, 0, Vec_IntSize(vUifConstrs) - 1, 0, vUifConstrs );
        // create reduction-AND node
        Vec_IntFill( vFanins, 1, iObjNew );
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_REDUCT_AND, 0, 0, 0, vFanins );
    }
    // update each PO to point to the new node
    Wlc_NtkForEachPo( p, pObj, i )
    {
        iObj = Wlc_ObjId(p, pObj);
        Vec_IntFillTwo( vFanins, 2, iObj, iObjNew );
        iObjNew = Wlc_ObjCreate( p, WLC_OBJ_LOGIC_AND, 0, 0, 0, vFanins );
        // note that a pointer to Wlc_Obj_t (for example, pObj) can be invalidated after a call to 
        // Wlc_ObjCreate() due to a possible realloc of the internal array of objects...
        pObj = Wlc_NtkObj( p, iObj );
        // update PO/CO arrays
        assert( Vec_IntEntry(&p->vPos, i) == iObj );
        assert( Vec_IntEntry(&p->vCos, i) == iObj );
        Vec_IntWriteEntry( &p->vPos, i, iObjNew );
        Vec_IntWriteEntry( &p->vCos, i, iObjNew );
        // transfer the PO attribute
        Wlc_NtkObj(p, iObjNew)->fIsPo = 1;
        assert( pObj->fIsPo );
        pObj->fIsPo = 0;
    }
    // cleanup
    Vec_IntFree( vUifConstrs );
    Vec_IntFree( vCompares );
    Vec_IntFree( vFanins );
    if ( vPairs != vPairsInit )
        Vec_IntFree( vPairs );
    // reconstruct topological order
    pNew = Wlc_NtkDupDfs( p, 0, 1 );
    return pNew;
}

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


ABC_NAMESPACE_IMPL_END

