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

  FileName    [gia.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Scalable AIG package.]

  Synopsis    []

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

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

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

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

#include "gia.h"
#include "map/if/if.h"

ABC_NAMESPACE_IMPL_START


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

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

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

  Synopsis    [Sorts the pins in the decreasing order of delays.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_LutDelayTraceSortPins( Gia_Man_t * p, int iObj, int * pPinPerm, float * pPinDelays )
{
    int iFanin, i, j, best_i, temp;
    assert( Gia_ObjIsLut(p, iObj) );
    // start the trivial permutation and collect pin delays
    Gia_LutForEachFanin( p, iObj, iFanin, i )
    {
        pPinPerm[i] = i;
        pPinDelays[i] = Gia_ObjTimeArrival(p, iFanin);
    }
    // selection sort the pins in the decreasible order of delays
    // this order will match the increasing order of LUT input pins
    for ( i = 0; i < Gia_ObjLutSize(p, iObj)-1; i++ )
    {
        best_i = i;
        for ( j = i+1; j < Gia_ObjLutSize(p, iObj); j++ )
            if ( pPinDelays[pPinPerm[j]] > pPinDelays[pPinPerm[best_i]] )
                best_i = j;
        if ( best_i == i )
            continue;
        temp = pPinPerm[i]; 
        pPinPerm[i] = pPinPerm[best_i]; 
        pPinPerm[best_i] = temp;
    }
    // verify
    assert( Gia_ObjLutSize(p, iObj) == 0 || pPinPerm[0] < Gia_ObjLutSize(p, iObj) );
    for ( i = 1; i < Gia_ObjLutSize(p, iObj); i++ )
    {
        assert( pPinPerm[i] < Gia_ObjLutSize(p, iObj) );
        assert( pPinDelays[pPinPerm[i-1]] >= pPinDelays[pPinPerm[i]] );
    }
}

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

  Synopsis    [Sorts the pins in the decreasing order of delays.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Gia_LutWhereIsPin( Gia_Man_t * p, int iFanout, int iFanin, int * pPinPerm )
{
    int i;
    for ( i = 0; i < Gia_ObjLutSize(p, iFanout); i++ )
        if ( Gia_ObjLutFanin(p, iFanout, pPinPerm[i]) == iFanin )
            return i;
    return -1;
}

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

  Synopsis    [Computes the arrival times for the given object.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
float Gia_ObjComputeArrival( Gia_Man_t * p, int iObj, int fUseSorting )
{
    If_LibLut_t * pLutLib = (If_LibLut_t *)p->pLutLib;
    Gia_Obj_t * pObj = Gia_ManObj( p, iObj );
    int k, iFanin, pPinPerm[32];
    float pPinDelays[32];
    float tArrival, * pDelays;
    if ( Gia_ObjIsCi(pObj) )
        return Gia_ObjTimeArrival(p, iObj);
    if ( Gia_ObjIsCo(pObj) )
        return Gia_ObjTimeArrival(p, Gia_ObjFaninId0p(p, pObj) );
    assert( Gia_ObjIsLut(p, iObj) );
    tArrival = -TIM_ETERNITY;
    if ( pLutLib == NULL )
    {
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( tArrival < Gia_ObjTimeArrival(p, iFanin) + 1.0 )
                tArrival = Gia_ObjTimeArrival(p, iFanin) + 1.0;
    }
    else if ( !pLutLib->fVarPinDelays )
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( tArrival < Gia_ObjTimeArrival(p, iFanin) + pDelays[0] )
                tArrival = Gia_ObjTimeArrival(p, iFanin) + pDelays[0];
    }
    else
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        if ( fUseSorting )
        {
            Gia_LutDelayTraceSortPins( p, iObj, pPinPerm, pPinDelays );
            Gia_LutForEachFanin( p, iObj, iFanin, k ) 
                if ( tArrival < Gia_ObjTimeArrival( p, Gia_ObjLutFanin(p,iObj,pPinPerm[k])) + pDelays[k] )
                    tArrival = Gia_ObjTimeArrival( p, Gia_ObjLutFanin(p,iObj,pPinPerm[k])) + pDelays[k];
        }
        else
        {
            Gia_LutForEachFanin( p, iObj, iFanin, k )
                if ( tArrival < Gia_ObjTimeArrival(p, iFanin) + pDelays[k] )
                    tArrival = Gia_ObjTimeArrival(p, iFanin) + pDelays[k];
        }
    }
    if ( Gia_ObjLutSize(p, iObj) == 0 )
        tArrival = 0.0;
    return tArrival;
}


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

  Synopsis    [Propagates the required times through the given node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
float Gia_ObjPropagateRequired( Gia_Man_t * p, int iObj, int fUseSorting )
{
    If_LibLut_t * pLutLib = (If_LibLut_t *)p->pLutLib;
    int k, iFanin, pPinPerm[32];
    float pPinDelays[32];
    float tRequired = 0.0; // Suppress "might be used uninitialized"
    float * pDelays;
    assert( Gia_ObjIsLut(p, iObj) );
    if ( pLutLib == NULL )
    {
        tRequired = Gia_ObjTimeRequired( p, iObj) - (float)1.0;
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( Gia_ObjTimeRequired(p, iFanin) > tRequired )
                Gia_ObjSetTimeRequired( p, iFanin, tRequired );
    }
    else if ( !pLutLib->fVarPinDelays )
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        tRequired = Gia_ObjTimeRequired(p, iObj) - pDelays[0];
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( Gia_ObjTimeRequired(p, iFanin) > tRequired )
                Gia_ObjSetTimeRequired( p, iFanin, tRequired );
    }
    else 
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        if ( fUseSorting )
        {
            Gia_LutDelayTraceSortPins( p, iObj, pPinPerm, pPinDelays );
            Gia_LutForEachFanin( p, iObj, iFanin, k )
            {
                tRequired = Gia_ObjTimeRequired( p, iObj) - pDelays[k];
                if ( Gia_ObjTimeRequired( p, Gia_ObjLutFanin(p, iObj,pPinPerm[k])) > tRequired )
                    Gia_ObjSetTimeRequired( p,  Gia_ObjLutFanin(p, iObj,pPinPerm[k]), tRequired );
            }
        }
        else
        {
            Gia_LutForEachFanin( p, iObj, iFanin, k )
            {
                tRequired = Gia_ObjTimeRequired(p, iObj) - pDelays[k];
                if ( Gia_ObjTimeRequired(p, iFanin) > tRequired )
                    Gia_ObjSetTimeRequired( p,  iFanin, tRequired );
            }
        }
    }
    return tRequired;
}

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

  Synopsis    [Computes the delay trace of the given network.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
float Gia_ManDelayTraceLut( Gia_Man_t * p )
{
    int fUseSorting = 1;
    If_LibLut_t * pLutLib = (If_LibLut_t *)p->pLutLib;
    Vec_Int_t * vObjs;
    Gia_Obj_t * pObj;
    float tArrival, tArrivalCur, tRequired, tSlack;
    int i, iObj;

    // get the library
    if ( pLutLib && pLutLib->LutMax < Gia_ManLutSizeMax(p) )
    {
        printf( "The max LUT size (%d) is less than the max fanin count (%d).\n", 
            pLutLib->LutMax, Gia_ManLutSizeMax(p) );
        return -TIM_ETERNITY;
    }

    // initialize the arrival times
    Gia_ManTimeStart( p );
    Gia_ManLevelNum( p );

    // propagate arrival times
    if ( p->pManTime )
        Tim_ManIncrementTravId( (Tim_Man_t *)p->pManTime );
    Gia_ManForEachObj( p, pObj, i )
    {
        if ( !Gia_ObjIsCi(pObj) && !Gia_ObjIsCo(pObj) && !Gia_ObjIsLut(p, i) )
            continue;
        tArrival = Gia_ObjComputeArrival( p, i, fUseSorting );
        if ( Gia_ObjIsCi(pObj) && p->pManTime )
        {
            tArrival = Tim_ManGetCiArrival( (Tim_Man_t *)p->pManTime, Gia_ObjCioId(pObj) );
//printf( "%.3f  ", tArrival );
        }
        if ( Gia_ObjIsCo(pObj) && p->pManTime )
            Tim_ManSetCoArrival( (Tim_Man_t *)p->pManTime, Gia_ObjCioId(pObj), tArrival );
        Gia_ObjSetTimeArrival( p, i, tArrival );
    }

    // get the latest arrival times
    tArrival = -TIM_ETERNITY;
    Gia_ManForEachCo( p, pObj, i )
    {
        tArrivalCur = Gia_ObjTimeArrivalObj( p, Gia_ObjFanin0(pObj) );
        Gia_ObjSetTimeArrival( p, Gia_ObjId(p,pObj), tArrivalCur );
        if ( tArrival < tArrivalCur )
            tArrival = tArrivalCur;
    }

    // initialize the required times
    if ( p->pManTime )
    {
        Tim_ManIncrementTravId( (Tim_Man_t *)p->pManTime );
        Tim_ManInitPoRequiredAll( (Tim_Man_t *)p->pManTime, tArrival );
    }
    else
    {
        Gia_ManForEachCo( p, pObj, i )
            Gia_ObjSetTimeRequiredObj( p, pObj, tArrival );
    }

    // propagate the required times
    vObjs = Gia_ManOrderReverse( p );
    Vec_IntForEachEntry( vObjs, iObj, i )
    {
        pObj = Gia_ManObj(p, iObj);
        if ( Gia_ObjIsLut(p, iObj) )
        {
            Gia_ObjPropagateRequired( p, iObj, fUseSorting );
        }
        else if ( Gia_ObjIsCi(pObj) )
        {
            if ( p->pManTime )
                Tim_ManSetCiRequired( (Tim_Man_t *)p->pManTime, Gia_ObjCioId(pObj), Gia_ObjTimeRequired(p, iObj) );
        }
        else if ( Gia_ObjIsCo(pObj) )
        {
            if ( p->pManTime )
            {
                tRequired = Tim_ManGetCoRequired( (Tim_Man_t *)p->pManTime, Gia_ObjCioId(pObj) );
                Gia_ObjSetTimeRequired( p, iObj, tRequired );
            }
            if ( Gia_ObjTimeRequired(p, Gia_ObjFaninId0p(p, pObj)) > Gia_ObjTimeRequired(p, iObj) )
                Gia_ObjSetTimeRequired(p, Gia_ObjFaninId0p(p, pObj), Gia_ObjTimeRequired(p, iObj) );
        }

        // set slack for this object
        tSlack = Gia_ObjTimeRequired(p, iObj) - Gia_ObjTimeArrival(p, iObj);
        assert( tSlack + 0.01 > 0.0 );
        Gia_ObjSetTimeSlack( p, iObj, tSlack < 0.0 ? 0.0 : tSlack );
    }
    Vec_IntFree( vObjs );
    return tArrival;
}

#if 0

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

  Synopsis    [Computes the required times for the given node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
float Gia_ObjComputeRequired( Gia_Man_t * p, int iObj, int fUseSorting )
{
    If_LibLut_t * pLutLib = p->pLutLib;
    int pPinPerm[32];
    float pPinDelays[32];
    Gia_Obj_t * pFanout;
    float tRequired, tDelay, * pDelays;
    int k, iFanin;
    if ( Gia_ObjIsCo(iObj) )
        return Gia_ObjTimeRequired( p, iObj);
    tRequired = TIM_ETERNITY;
    if ( pLutLib == NULL )
    {
        Gia_ObjForEachFanout( iObj, pFanout, k )
        {
            tDelay = Gia_ObjIsCo(pFanout)? 0.0 : 1.0;
            if ( tRequired > Gia_ObjTimeRequired( p, pFanout) - tDelay )
                tRequired = Gia_ObjTimeRequired( p, pFanout) - tDelay;
        }
    }
    else if ( !pLutLib->fVarPinDelays )
    {
        Gia_ObjForEachFanout( iObj, pFanout, k )
        {
            pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, pFanout)];
            tDelay = Gia_ObjIsCo(pFanout)? 0.0 : pDelays[0];
            if ( tRequired > Gia_ObjTimeRequired( p, pFanout) - tDelay )
                tRequired = Gia_ObjTimeRequired( p, pFanout) - tDelay;
        }
    }
    else
    {
        if ( fUseSorting )
        {
            Gia_ObjForEachFanout( iObj, pFanout, k ) 
            {
                pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, pFanout)];
                Gia_LutDelayTraceSortPins( p, pFanout, pPinPerm, pPinDelays );
                iFanin = Gia_LutWhereIsPin( p, pFanout, iObj, pPinPerm );
                assert( Gia_ObjLutFanin( p, pFanout, pPinPerm[iFanin]) == iObj );
                tDelay = Gia_ObjIsCo(pFanout)? 0.0 : pDelays[iFanin];
                if ( tRequired > Gia_ObjTimeRequired( p, pFanout) - tDelay )
                    tRequired = Gia_ObjTimeRequired( p, pFanout) - tDelay;
            }
        }
        else
        {
            Gia_ObjForEachFanout( iObj, pFanout, k )
            {
                pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, pFanout)];
                iFanin = Gia_ObjFindFanin( p, pFanout, iObj );
                assert( Gia_ObjLutFanin(p, pFanout, iFanin) == iObj );
                tDelay = Gia_ObjIsCo(pFanout)? 0.0 : pDelays[iFanin];
                if ( tRequired > Gia_ObjTimeRequired( p, pFanout) - tDelay )
                    tRequired = Gia_ObjTimeRequired( p, pFanout) - tDelay;
            }
        }
    }
    return tRequired;
}

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

  Synopsis    [Computes the arrival times for the given node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Gia_LutVerifyTiming(  Gia_Man_t * p )
{
    int iObj;
    float tArrival, tRequired;
    int i;
    Gia_LutForEachObj( p, iObj, i )
    {
        if ( Gia_ObjIsCi(iObj) && Gia_ObjFanoutNum(iObj) == 0 )
            continue;
        tArrival = Gia_ObjComputeArrival( p, iObj, 1 );
        tRequired = Gia_ObjComputeRequired( p, iObj, 1 );
        if ( !Gia_LutTimeEqual( tArrival, Gia_ObjTimeArrival( p, iObj), (float)0.01 ) )
            printf( "Gia_LutVerifyTiming(): Object %d has different arrival time (%.2f) from computed (%.2f).\n", 
                iObj->Id, Gia_ObjTimeArrival( p, iObj), tArrival );
        if ( !Gia_LutTimeEqual( tRequired, Gia_ObjTimeRequired( p, iObj), (float)0.01 ) )
            printf( "Gia_LutVerifyTiming(): Object %d has different required time (%.2f) from computed (%.2f).\n", 
                iObj->Id, Gia_ObjTimeRequired( p, iObj), tRequired );
    }
    return 1;
}

#endif

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

  Synopsis    [Prints the delay trace for the given network.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
float Gia_ManDelayTraceLutPrint( Gia_Man_t * p, int fVerbose )
{
    If_LibLut_t * pLutLib = (If_LibLut_t *)p->pLutLib;
    int i, Nodes, * pCounters;
    float tArrival, tDelta, nSteps, Num;
    // get the library
    if ( pLutLib && pLutLib->LutMax < Gia_ManLutSizeMax(p) )
    {
        printf( "The max LUT size (%d) is less than the max fanin count (%d).\n", 
            pLutLib->LutMax, Gia_ManLutSizeMax(p) );
        return -ABC_INFINITY;
    }
    // decide how many steps
    nSteps = pLutLib ? 20 : Gia_ManLutLevel(p, NULL);
    pCounters = ABC_ALLOC( int, nSteps + 1 );
    memset( pCounters, 0, sizeof(int)*(nSteps + 1) );
    // perform delay trace
    tArrival = Gia_ManDelayTraceLut( p );
    tDelta   = tArrival / nSteps;
    // count how many nodes have slack in the corresponding intervals
    Gia_ManForEachLut( p, i )
    {
        if ( Gia_ObjLutSize(p, i) == 0 )
            continue;
        Num = Gia_ObjTimeSlack(p, i) / tDelta;
        if ( Num > nSteps )
            continue;
        assert( Num >=0 && Num <= nSteps );
        pCounters[(int)Num]++;
    }
    // print the results    
    if ( fVerbose )
    {
        printf( "Max delay = %6.2f. Delay trace using %s model:\n", tArrival, pLutLib? "LUT library" : "unit-delay" );
        Nodes = 0;
        for ( i = 0; i < nSteps; i++ )
        {
            Nodes += pCounters[i];
            printf( "%3d %s : %5d  (%6.2f %%)\n", pLutLib? 5*(i+1) : i+1, 
                pLutLib? "%":"lev", Nodes, 100.0*Nodes/Gia_ManLutNum(p) );
        }
    }
    ABC_FREE( pCounters );
    Gia_ManTimeStop( p );
    return tArrival;
}

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

  Synopsis    [Determines timing-critical edges of the node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
unsigned Gia_LutDelayTraceTCEdges( Gia_Man_t * p, int iObj, float tDelta )
{
    If_LibLut_t * pLutLib = (If_LibLut_t *)p->pLutLib;
    int pPinPerm[32];
    float pPinDelays[32];
    float tRequired, * pDelays;
    unsigned uResult = 0;
    int k, iFanin;
    tRequired = Gia_ObjTimeRequired( p, iObj );
    if ( pLutLib == NULL )
    {
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( tRequired < Gia_ObjTimeArrival(p, iFanin) + 1.0 + tDelta )
                uResult |= (1 << k);
    }
    else if ( !pLutLib->fVarPinDelays )
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( tRequired < Gia_ObjTimeArrival(p, iFanin) + pDelays[0] + tDelta )
                uResult |= (1 << k);
    }
    else
    {
        pDelays = pLutLib->pLutDelays[Gia_ObjLutSize(p, iObj)];
        Gia_LutDelayTraceSortPins( p, iObj, pPinPerm, pPinDelays );
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( tRequired < Gia_ObjTimeArrival( p, Gia_ObjLutFanin(p, iObj,pPinPerm[k])) + pDelays[k] + tDelta )
                uResult |= (1 << pPinPerm[k]);
    }
    return uResult;
}

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

  Synopsis    [Adds strashed nodes for one node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Gia_ManSpeedupObj_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vNodes )
{
    if ( Gia_ObjIsTravIdCurrent(p, pObj) )
        return 1;
    Gia_ObjSetTravIdCurrent( p, pObj );
    if ( Gia_ObjIsCi(pObj) )
        return 0;
    assert( Gia_ObjIsAnd(pObj) );
    if ( !Gia_ManSpeedupObj_rec( p, Gia_ObjFanin0(pObj), vNodes ) )
        return 0;
    if ( !Gia_ManSpeedupObj_rec( p, Gia_ObjFanin1(pObj), vNodes ) )
        return 0;
    Vec_IntPush( vNodes, Gia_ObjId(p, pObj) );
    return 1;
}

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

  Synopsis    [Adds strashed nodes for one node.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_ManSpeedupObj( Gia_Man_t * pNew, Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vLeaves, Vec_Int_t * vTimes )
{
    Vec_Int_t * vNodes;
    Gia_Obj_t * pTemp = NULL;
    int pCofs[32], nCofs, nSkip, i, k, iResult, iObj;
    // mark the leaves
    Gia_ManIncrementTravId( p );
    Gia_ObjSetTravIdCurrent( p, Gia_ManConst0(p) );
    Gia_ManForEachObjVec( vLeaves, p, pTemp, i )
        Gia_ObjSetTravIdCurrent( p, pTemp );
    // collect the AIG nodes
    vNodes = Vec_IntAlloc( 100 );
    if ( !Gia_ManSpeedupObj_rec( p, pObj, vNodes ) )
    {
        printf( "Bad node!!!\n" );
        Vec_IntFree( vNodes );
        return;
    }
    // derive cofactors
    nCofs = (1 << Vec_IntSize(vTimes));
    for ( i = 0; i < nCofs; i++ )
    {
        Gia_ManForEachObjVec( vLeaves, p, pTemp, k )
            pTemp->Value = Abc_Var2Lit( Gia_ObjId(p, pTemp), 0 );
        Gia_ManForEachObjVec( vTimes, p, pTemp, k )
            pTemp->Value = ((i & (1<<k)) != 0);
        Gia_ManForEachObjVec( vNodes, p, pTemp, k )
            pTemp->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pTemp), Gia_ObjFanin1Copy(pTemp) );
        pCofs[i] = pTemp->Value;
    }
    Vec_IntFree( vNodes );
    // collect the resulting tree
    Gia_ManForEachObjVec( vTimes, p, pTemp, k )
        for ( nSkip = (1<<k), i = 0; i < nCofs; i += 2*nSkip )
            pCofs[i] = Gia_ManHashMux( pNew, Gia_ObjToLit(p,pTemp), pCofs[i+nSkip], pCofs[i] );
    // create choice node  (pObj is repr and ppCofs[0] is new)
    iObj    = Gia_ObjId( p, pObj );
    iResult = Abc_Lit2Var( pCofs[0] );
    if ( iResult <= iObj )
        return;
    Gia_ObjSetRepr( pNew, iResult, iObj );
    Gia_ObjSetNext( pNew, iResult, Gia_ObjNext(pNew, iObj) );
    Gia_ObjSetNext( pNew, iObj, iResult );
}

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

  Synopsis    [Adds choices to speed up the network by the given percentage.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManSpeedup( Gia_Man_t * p, int Percentage, int Degree, int fVerbose, int fVeryVerbose )
{
    Gia_Man_t * pNew, * pTemp;
    Vec_Int_t * vTimeCries, * vTimeFanins;
    int iObj, iFanin, iFanin2, nNodesNew;
    float tDelta, tArrival;
    int i, k, k2, Counter, CounterRes, nTimeCris;
    int fUseLutLib = (p->pLutLib != NULL);
    void * pTempTim = NULL;
    unsigned * puTCEdges;
    assert( Gia_ManHasMapping(p) );
    if ( !fUseLutLib && p->pManTime )
    {
        pTempTim = p->pManTime;
        p->pManTime = Tim_ManDup( (Tim_Man_t *)pTempTim, 1 );
    }
    // perform delay trace
    tArrival = Gia_ManDelayTraceLut( p );
    tDelta = fUseLutLib ? tArrival*Percentage/100.0 : 1.0;
    if ( fVerbose )
    {
        printf( "Max delay = %.2f. Delta = %.2f. ", tArrival, tDelta );
        printf( "Using %s model. ", fUseLutLib ? "LUT library" : "unit-delay" );
        if ( fUseLutLib )
            printf( "Percentage = %d. ", Percentage );
        printf( "\n" );
    }
    // mark the timing critical nodes and edges
    puTCEdges = ABC_CALLOC( unsigned, Gia_ManObjNum(p) );
    Gia_ManForEachLut( p, iObj )
    {
        if ( Gia_ObjTimeSlack(p, iObj) >= tDelta )
            continue;
        puTCEdges[iObj] = Gia_LutDelayTraceTCEdges( p, iObj, tDelta );
    }
    if ( fVerbose )
    {
        Counter = CounterRes = 0;
        Gia_ManForEachLut( p, iObj )
        {
            Gia_LutForEachFanin( p, iObj, iFanin, k )
                if ( !Gia_ObjIsCi(Gia_ManObj(p, iFanin)) && Gia_ObjTimeSlack(p, iFanin) < tDelta )
                    Counter++;
            CounterRes += Gia_WordCountOnes( puTCEdges[iObj] );
        }
        printf( "Edges: Total = %7d. 0-slack = %7d. Critical = %7d. Ratio = %4.2f\n", 
            Gia_ManLutFaninCount(p), Counter, CounterRes, Counter? 1.0*CounterRes/Counter : 0.0 );
    }

    // start the resulting network
    pNew = Gia_ManDup( p );
    Gia_ManHashStart( pNew );
    nNodesNew = 1000 + 3 * Gia_ManObjNum(pNew);
    pNew->pNexts = ABC_CALLOC( int, nNodesNew );
    pNew->pReprs = ABC_CALLOC( Gia_Rpr_t, nNodesNew );
    for ( i = 0; i < nNodesNew; i++ )
        Gia_ObjSetRepr( pNew, i, GIA_VOID );

    // collect nodes to be used for resynthesis
    Counter = CounterRes = 0;
    vTimeCries = Vec_IntAlloc( 16 );
    vTimeFanins = Vec_IntAlloc( 16 );
    Gia_ManForEachLut( p, iObj )
    {
        if ( Gia_ObjTimeSlack(p, iObj) >= tDelta )
            continue;
        // count the number of non-PI timing-critical nodes
        nTimeCris = 0;
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            if ( !Gia_ObjIsCi(Gia_ManObj(p, iFanin)) && (puTCEdges[iObj] & (1<<k)) )
                nTimeCris++;
        if ( !fVeryVerbose && nTimeCris == 0 )
            continue;
        Counter++;
        // count the total number of timing critical second-generation nodes
        Vec_IntClear( vTimeCries );
        if ( nTimeCris )
        {
            Gia_LutForEachFanin( p, iObj, iFanin, k )
                if ( !Gia_ObjIsCi(Gia_ManObj(p, iFanin)) && (puTCEdges[iObj] & (1<<k)) )
                    Gia_LutForEachFanin( p, iFanin, iFanin2, k2 )
                        if ( puTCEdges[iFanin] & (1<<k2) )
                            Vec_IntPushUnique( vTimeCries, iFanin2 );
        }
//        if ( !fVeryVerbose && (Vec_IntSize(vTimeCries) == 0 || Vec_IntSize(vTimeCries) > Degree) )
        if ( (Vec_IntSize(vTimeCries) == 0 || Vec_IntSize(vTimeCries) > Degree) )
            continue;
        CounterRes++;
        // collect second generation nodes
        Vec_IntClear( vTimeFanins );
        Gia_LutForEachFanin( p, iObj, iFanin, k )
        {
            if ( Gia_ObjIsCi(Gia_ManObj(p, iFanin)) )
                Vec_IntPushUnique( vTimeFanins, iFanin );
            else
                Gia_LutForEachFanin( p, iFanin, iFanin2, k2 )
                    Vec_IntPushUnique( vTimeFanins, iFanin2 );                    
        }
        // print the results
        if ( fVeryVerbose )
        {
        printf( "%5d Node %5d : %d %2d %2d  ", Counter, iObj, 
            nTimeCris, Vec_IntSize(vTimeCries), Vec_IntSize(vTimeFanins) );
        Gia_LutForEachFanin( p, iObj, iFanin, k )
            printf( "%d(%.2f)%s ", iFanin, Gia_ObjTimeSlack(p, iFanin), (puTCEdges[iObj] & (1<<k))? "*":"" );
        printf( "\n" );
        }
        // add the node to choices
        if ( Vec_IntSize(vTimeCries) == 0 || Vec_IntSize(vTimeCries) > Degree )
            continue;
        // order the fanins in the increasing order of criticalily
        if ( Vec_IntSize(vTimeCries) > 1 )
        {
            iFanin = Vec_IntEntry( vTimeCries, 0 );
            iFanin2 = Vec_IntEntry( vTimeCries, 1 );
            if ( Gia_ObjTimeSlack(p, iFanin) < Gia_ObjTimeSlack(p, iFanin2) )
            {
                Vec_IntWriteEntry( vTimeCries, 0, iFanin2 );
                Vec_IntWriteEntry( vTimeCries, 1, iFanin );
            }
        }
        if ( Vec_IntSize(vTimeCries) > 2 )
        {
            iFanin = Vec_IntEntry( vTimeCries, 1 );
            iFanin2 = Vec_IntEntry( vTimeCries, 2 );
            if ( Gia_ObjTimeSlack(p, iFanin) < Gia_ObjTimeSlack(p, iFanin2) )
            {
                Vec_IntWriteEntry( vTimeCries, 1, iFanin2 );
                Vec_IntWriteEntry( vTimeCries, 2, iFanin );
            }
            iFanin = Vec_IntEntry( vTimeCries, 0 );
            iFanin2 = Vec_IntEntry( vTimeCries, 1 );
            if ( Gia_ObjTimeSlack(p, iFanin) < Gia_ObjTimeSlack(p, iFanin2) )
            {
                Vec_IntWriteEntry( vTimeCries, 0, iFanin2 );
                Vec_IntWriteEntry( vTimeCries, 1, iFanin );
            }
        }
        // add choice
        Gia_ManSpeedupObj( pNew, p, Gia_ManObj(p,iObj), vTimeFanins, vTimeCries );
        // quit if the number of nodes is large
        if ( Gia_ManObjNum(pNew) > nNodesNew - 100 )
        {
            printf( "Speedup stopped adding choices because there was too many to add.\n" );
            break;
        }
    }
    Gia_ManTimeStop( p );
    Vec_IntFree( vTimeCries );
    Vec_IntFree( vTimeFanins );
    ABC_FREE( puTCEdges );
    if ( fVerbose )
        printf( "Nodes: Total = %7d. 0-slack = %7d. Workable = %7d. Ratio = %4.2f\n", 
        Gia_ManLutNum(p), Counter, CounterRes, Counter? 1.0*CounterRes/Counter : 0.0 ); 
    if ( pTempTim )
    {
        Tim_ManStop( (Tim_Man_t *)p->pManTime );
        p->pManTime = pTempTim;
    }
    // derive AIG with choices
//Gia_ManPrintStats( pNew, 0 );
    pTemp = Gia_ManEquivToChoices( pNew, 1 );
    Gia_ManStop( pNew );
//Gia_ManPrintStats( pTemp, 0 );
//    pNew = Gia_ManDupOrderDfsChoices( pTemp );
//    Gia_ManStop( pTemp );
//Gia_ManPrintStats( pNew, 0 );
//    return pNew;
    return pTemp;
}

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


ABC_NAMESPACE_IMPL_END

