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

  FileName    [darCut.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [DAG-aware AIG rewriting.]

  Synopsis    [Computation of 4-input cuts.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - April 28, 2007.]

  Revision    [$Id: darCut.c,v 1.00 2007/04/28 00:00:00 alanmi Exp $]

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

#include "dar.h"

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

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

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

  Synopsis    [Returns 1 if pDom is contained in pCut.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Dar_CutCheckDominance( Dar_Cut_t * pDom, Dar_Cut_t * pCut )
{
    int i, k;
    for ( i = 0; i < (int)pDom->nLeaves; i++ )
    {
        for ( k = 0; k < (int)pCut->nLeaves; k++ )
            if ( pDom->pLeaves[i] == pCut->pLeaves[k] )
                break;
        if ( k == (int)pCut->nLeaves ) // node i in pDom is not contained in pCut
            return 0;
    }
    // every node in pDom is contained in pCut
    return 1;
}

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

  Synopsis    [Returns 1 if the cut is contained.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Dar_CutFilter( Dar_Man_t * p, Dar_Cut_t * pCut )
{ 
    Dar_Cut_t * pTemp;
    int i, k;
    assert( p->pBaseCuts[p->nCutsUsed] == pCut );
    for ( i = 0; i < p->nCutsUsed; i++ )
    {
        pTemp = p->pBaseCuts[i];
        if ( pTemp->nLeaves > pCut->nLeaves )
        {
            // skip the non-contained cuts
            if ( (pTemp->uSign & pCut->uSign) != pCut->uSign )
                continue;
            // check containment seriously
            if ( Dar_CutCheckDominance( pCut, pTemp ) )
            {
//                p->ppCuts[i] = p->ppCuts[p->nCuts-1];
//                p->ppCuts[p->nCuts-1] = pTemp;
//                p->nCuts--;
//                i--;
                // remove contained cut
                for ( k = i; k < p->nCutsUsed; k++ )
                    p->pBaseCuts[k] = p->pBaseCuts[k+1];
                p->pBaseCuts[p->nCutsUsed] = pTemp;
                p->nCutsUsed--;
                i--;
                p->nCutsFiltered++;
            }
         }
        else
        {
            // skip the non-contained cuts
            if ( (pTemp->uSign & pCut->uSign) != pTemp->uSign )
                continue;
            // check containment seriously
            if ( Dar_CutCheckDominance( pTemp, pCut ) )
            {
                p->nCutsFiltered++;
                return 1;
            }
        }
    }
    return 0;
}

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

  Synopsis    [Merges two cuts.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Dar_CutMergeOrdered( Dar_Cut_t * pC, Dar_Cut_t * pC0, Dar_Cut_t * pC1 )
{ 
    int i, k, c;
    assert( pC0->nLeaves >= pC1->nLeaves );

    // the case of the largest cut sizes
    if ( pC0->nLeaves == 4 && pC1->nLeaves == 4 )
    {
        if ( pC0->uSign != pC1->uSign )
            return 0;
        for ( i = 0; i < (int)pC0->nLeaves; i++ )
            if ( pC0->pLeaves[i] != pC1->pLeaves[i] )
                return 0;
        for ( i = 0; i < (int)pC0->nLeaves; i++ )
            pC->pLeaves[i] = pC0->pLeaves[i];
        pC->nLeaves = pC0->nLeaves;
        return 1;
    }

    // the case when one of the cuts is the largest
    if ( pC0->nLeaves == 4 )
    {
        if ( (pC0->uSign & pC1->uSign) != pC1->uSign )
            return 0;
        for ( i = 0; i < (int)pC1->nLeaves; i++ )
        {
            for ( k = (int)pC0->nLeaves - 1; k >= 0; k-- )
                if ( pC0->pLeaves[k] == pC1->pLeaves[i] )
                    break;
            if ( k == -1 ) // did not find
                return 0;
        }
        for ( i = 0; i < (int)pC0->nLeaves; i++ )
            pC->pLeaves[i] = pC0->pLeaves[i];
        pC->nLeaves = pC0->nLeaves;
        return 1;
    }

    // compare two cuts with different numbers
    i = k = 0;
    for ( c = 0; c < 4; c++ )
    {
        if ( k == (int)pC1->nLeaves )
        {
            if ( i == (int)pC0->nLeaves )
            {
                pC->nLeaves = c;
                return 1;
            }
            pC->pLeaves[c] = pC0->pLeaves[i++];
            continue;
        }
        if ( i == (int)pC0->nLeaves )
        {
            if ( k == (int)pC1->nLeaves )
            {
                pC->nLeaves = c;
                return 1;
            }
            pC->pLeaves[c] = pC1->pLeaves[k++];
            continue;
        }
        if ( pC0->pLeaves[i] < pC1->pLeaves[k] )
        {
            pC->pLeaves[c] = pC0->pLeaves[i++];
            continue;
        }
        if ( pC0->pLeaves[i] > pC1->pLeaves[k] )
        {
            pC->pLeaves[c] = pC1->pLeaves[k++];
            continue;
        }
        pC->pLeaves[c] = pC0->pLeaves[i++]; 
        k++;
    }
    if ( i < (int)pC0->nLeaves || k < (int)pC1->nLeaves )
        return 0;
    pC->nLeaves = c;
    return 1;
}

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

  Synopsis    [Prepares the object for FPGA mapping.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Dar_CutMerge( Dar_Cut_t * pCut, Dar_Cut_t * pCut0, Dar_Cut_t * pCut1 )
{ 
    // merge the nodes
    if ( pCut0->nLeaves <= pCut1->nLeaves )
    {
        if ( !Dar_CutMergeOrdered( pCut, pCut1, pCut0 ) )
            return 0;
    }
    else
    {
        if ( !Dar_CutMergeOrdered( pCut, pCut0, pCut1 ) )
            return 0;
    }
    pCut->uSign = pCut0->uSign | pCut1->uSign;
    return 1;
}


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

  Synopsis    [Computes the stretching phase of the cut w.r.t. the merged cut.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Dar_CutTruthPhase( Dar_Cut_t * pCut, Dar_Cut_t * pCut1 )
{
    unsigned uPhase = 0;
    int i, k;
    for ( i = k = 0; i < (int)pCut->nLeaves; i++ )
    {
        if ( k == (int)pCut1->nLeaves )
            break;
        if ( pCut->pLeaves[i] < pCut1->pLeaves[k] )
            continue;
        assert( pCut->pLeaves[i] == pCut1->pLeaves[k] );
        uPhase |= (1 << i);
        k++;
    }
    return uPhase;
}

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

  Synopsis    [Swaps two advancent variables of the truth table.]

  Description [Swaps variable iVar and iVar+1.]
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Dar_CutTruthSwapAdjacentVars( unsigned uTruth, int iVar )
{
    assert( iVar >= 0 && iVar <= 2 );
    if ( iVar == 0 )
        return (uTruth & 0x99999999) | ((uTruth & 0x22222222) << 1) | ((uTruth & 0x44444444) >> 1);
    if ( iVar == 1 )
        return (uTruth & 0xC3C3C3C3) | ((uTruth & 0x0C0C0C0C) << 2) | ((uTruth & 0x30303030) >> 2);
    if ( iVar == 2 )
        return (uTruth & 0xF00FF00F) | ((uTruth & 0x00F000F0) << 4) | ((uTruth & 0x0F000F00) >> 4);
    assert( 0 );
    return 0;
}

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

  Synopsis    [Expands the truth table according to the phase.]

  Description [The input and output truth tables are in pIn/pOut. The current number
  of variables is nVars. The total number of variables in nVarsAll. The last argument
  (Phase) contains shows where the variables should go.]
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Dar_CutTruthStretch( unsigned uTruth, int nVars, unsigned Phase )
{
    int i, k, Var = nVars - 1;
    for ( i = 3; i >= 0; i-- )
        if ( Phase & (1 << i) )
        {
            for ( k = Var; k < i; k++ )
                uTruth = Dar_CutTruthSwapAdjacentVars( uTruth, k );
            Var--;
        }
    assert( Var == -1 );
    return uTruth;
}

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

  Synopsis    [Shrinks the truth table according to the phase.]

  Description [The input and output truth tables are in pIn/pOut. The current number
  of variables is nVars. The total number of variables in nVarsAll. The last argument
  (Phase) contains shows what variables should remain.]
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Dar_CutTruthShrink( unsigned uTruth, int nVars, unsigned Phase )
{
    int i, k, Var = 0;
    for ( i = 0; i < 4; i++ )
        if ( Phase & (1 << i) )
        {
            for ( k = i-1; k >= Var; k-- )
                uTruth = Dar_CutTruthSwapAdjacentVars( uTruth, k );
            Var++;
        }
    return uTruth;
}

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

  Synopsis    [Performs truth table computation.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Dar_CutTruth( Dar_Cut_t * pCut, Dar_Cut_t * pCut0, Dar_Cut_t * pCut1, int fCompl0, int fCompl1 )
{
    unsigned uTruth0 = fCompl0 ? ~pCut0->uTruth : pCut0->uTruth;
    unsigned uTruth1 = fCompl1 ? ~pCut1->uTruth : pCut1->uTruth;
    uTruth0 = Dar_CutTruthStretch( uTruth0, pCut0->nLeaves, Dar_CutTruthPhase(pCut, pCut0) );
    uTruth1 = Dar_CutTruthStretch( uTruth1, pCut1->nLeaves, Dar_CutTruthPhase(pCut, pCut1) );
    return uTruth0 & uTruth1;
}

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

  Synopsis    [Minimize support of the cut.]

  Description [Returns 1 if the node's support has changed]
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline int Dar_CutSuppMinimize( Dar_Cut_t * pCut )
{
    unsigned uMasks[4][2] = {
        { 0x5555, 0xAAAA },
        { 0x3333, 0xCCCC },
        { 0x0F0F, 0xF0F0 },
        { 0x00FF, 0xFF00 }
    };
    unsigned uPhase = 0, uTruth = 0xFFFF & pCut->uTruth;
    int i, k, nLeaves;
    // compute the truth support of the cut's function
    nLeaves = pCut->nLeaves;
    for ( i = 0; i < (int)pCut->nLeaves; i++ )
        if ( (uTruth & uMasks[i][0]) == ((uTruth & uMasks[i][1]) >> (1 << i)) )
            nLeaves--;
        else
            uPhase |= (1 << i);
    if ( nLeaves == (int)pCut->nLeaves )
        return 0;
    // shrink the truth table
    uTruth = Dar_CutTruthShrink( uTruth, pCut->nLeaves, uPhase );
    pCut->uTruth = 0xFFFF & uTruth;
    // update leaves and signature
    pCut->uSign = 0;
    for ( i = k = 0; i < (int)pCut->nLeaves; i++ )
    {
        if ( !(uPhase & (1 << i)) )
            continue;    
        pCut->pLeaves[k] = pCut->pLeaves[i];
//        pCut->pIndices[k] = pCut->pIndices[i];
        pCut->uSign |= (1 << (pCut->pLeaves[i] & 31));
        k++;
    }
    assert( k == nLeaves );
    pCut->nLeaves = nLeaves;
    return 1;
}

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

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Dar_ObjSetupTrivial( Dar_Obj_t * pObj )
{
    Dar_Cut_t * pCut;
    pCut = pObj->pData;
    memset( pCut, 0, sizeof(Dar_Cut_t) );
    pCut->nLeaves = 1;
    pCut->pLeaves[0] = pObj->Id;
//    pCut->pIndices[0] = 0;
    pCut->uSign = (1 << (pObj->Id & 31));
    pCut->uTruth = 0xAAAA;
}

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

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Dar_ManSetupPis( Dar_Man_t * p )
{
    Dar_Obj_t * pObj;
    int i;
    Dar_ManForEachPi( p, pObj, i )
    {
        pObj->nCuts = 1;
        pObj->pData = (Dar_Cut_t *)Dar_MmFlexEntryFetch( p->pMemCuts, pObj->nCuts * sizeof(Dar_Cut_t) );
        Dar_ObjSetupTrivial( pObj );
    }
}

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

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Dar_ManCutsFree( Dar_Man_t * p )
{
//    Dar_ManCleanData( p );
    Dar_MmFlexStop( p->pMemCuts, 0 );
    p->pMemCuts = NULL;
}

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

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Dar_Cut_t * Dar_ObjComputeCuts( Dar_Man_t * p, Dar_Obj_t * pObj )
{
    Dar_Obj_t * pFanin0 = Dar_ObjReal_rec( Dar_ObjChild0(pObj) );
    Dar_Obj_t * pFanin1 = Dar_ObjReal_rec( Dar_ObjChild1(pObj) );
    Dar_Cut_t * pStart0 = Dar_Regular(pFanin0)->pData;
    Dar_Cut_t * pStart1 = Dar_Regular(pFanin1)->pData;
    Dar_Cut_t * pStop0 = pStart0 + Dar_Regular(pFanin0)->nCuts;
    Dar_Cut_t * pStop1 = pStart1 + Dar_Regular(pFanin1)->nCuts;
    Dar_Cut_t * pCut0, * pCut1, * pCut;
    int i;
    assert( pObj->pData == NULL );
    // make sure fanins cuts are computed
    p->nCutsUsed = 0;
    for ( pCut0 = pStart0; pCut0 < pStop0; pCut0++ )
    for ( pCut1 = pStart1; pCut1 < pStop1; pCut1++ )
    {
        // get storage for the next cut
        if ( p->nCutsUsed == p->nBaseCuts )
            break;
        pCut = p->pBaseCuts[p->nCutsUsed];
        // create the new cut
        if ( !Dar_CutMerge( pCut, pCut0, pCut1 ) )
            continue;
        // check dominance
        if ( Dar_CutFilter( p, pCut ) )
            continue;
        // compute truth table
        pCut->uTruth = 0xFFFF & Dar_CutTruth( pCut, pCut0, pCut1, Dar_IsComplement(pFanin0), Dar_IsComplement(pFanin1) );

        // minimize support of the cut
        if ( Dar_CutSuppMinimize( pCut ) )
        {
            if ( Dar_CutFilter( p, pCut ) )
                continue;
        }

        // CNF mapping: assign the cut cost 
        if ( p->pManCnf )
        {
            extern void Cnf_CutAssignAreaFlow( void * pManCnf, Dar_Cut_t * pCut );
            Cnf_CutAssignAreaFlow( p->pManCnf, pCut );
        }

        // increment the number of cuts
        p->nCutsUsed++;
    }
    // get memory for the cuts of this node
    pObj->nCuts = p->nCutsUsed + 1;
    pObj->pData = pCut = (Dar_Cut_t *)Dar_MmFlexEntryFetch( p->pMemCuts, pObj->nCuts * sizeof(Dar_Cut_t) );
    // create elementary cut
    Dar_ObjSetupTrivial( pObj );
    // copy non-elementary cuts
    for ( i = 0; i < p->nCutsUsed; i++ )
        *++pCut = *(p->pBaseCuts[i]);

    // CNF mapping: assign the best cut 
    if ( p->pManCnf )
    {
        extern Dar_Cut_t * Cnf_ObjFindBestCut( Dar_Obj_t * pObj );
        Dar_ObjSetBestCut( Cnf_ObjFindBestCut(pObj) );
    }
    return pObj->pData;
}

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

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Dar_Cut_t * Dar_ObjComputeCuts_rec( Dar_Man_t * p, Dar_Obj_t * pObj )
{
    if ( pObj->pData )
        return pObj->pData;
    if ( Dar_ObjIsBuf(pObj) )
        return Dar_ObjComputeCuts_rec( p, Dar_ObjFanin0(pObj) );
    Dar_ObjComputeCuts_rec( p, Dar_ObjFanin0(pObj) );
    Dar_ObjComputeCuts_rec( p, Dar_ObjFanin1(pObj) );
    return Dar_ObjComputeCuts( p, pObj );
}

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


