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

  FileName    [bbrReach.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [BDD-based reachability analysis.]

  Synopsis    [Procedures to perform reachability analysis.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

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

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

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

#include "bbr.h"

ABC_NAMESPACE_IMPL_START


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

extern Abc_Cex_t * Aig_ManVerifyUsingBddsCountExample( Aig_Man_t * p, DdManager * dd, 
    DdNode ** pbParts, Vec_Ptr_t * vOnionRings, DdNode * bCubeFirst, 
    int iOutput, int fVerbose, int fSilent );

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

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

  Synopsis    [This procedure sets default resynthesis parameters.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Bbr_ManSetDefaultParams( Saig_ParBbr_t * p )
{
    memset( p, 0, sizeof(Saig_ParBbr_t) );
    p->TimeLimit     =     0;
    p->nBddMax       = 50000;
    p->nIterMax      =  1000;
    p->fPartition    =     1;
    p->fReorder      =     1;
    p->fReorderImage =     1;
    p->fVerbose      =     0;
    p->fSilent       =     0;
    p->iFrame        =    -1;
}

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

  Synopsis    [Performs the reordering-sensitive step of Extra_bddMove().]

  Description []

  SideEffects []

  SeeAlso     []

******************************************************************************/
DdNode * Bbr_bddComputeRangeCube( DdManager * dd, int iStart, int iStop )
{
    DdNode * bTemp, * bProd;
    int i;
    assert( iStart <= iStop );
    assert( iStart >= 0 && iStart <= dd->size );
    assert( iStop >= 0  && iStop  <= dd->size );
    bProd = (dd)->one;         Cudd_Ref( bProd );
    for ( i = iStart; i < iStop; i++ )
    {
        bProd = Cudd_bddAnd( dd, bTemp = bProd, dd->vars[i] );      Cudd_Ref( bProd );
        Cudd_RecursiveDeref( dd, bTemp ); 
    }
    Cudd_Deref( bProd );
    return bProd;
}

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

  Synopsis    []

  Description []

  SideEffects []

  SeeAlso     []

***********************************************************************/
void Bbr_StopManager( DdManager * dd )
{
    int RetValue;
    // check for remaining references in the package
    RetValue = Cudd_CheckZeroRef( dd );
    if ( RetValue > 0 )
        printf( "\nThe number of referenced nodes = %d\n\n", RetValue );
//  Cudd_PrintInfo( dd, stdout );
    Cudd_Quit( dd );
}

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

  Synopsis    [Computes the initial state and sets up the variable map.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
DdNode * Aig_ManInitStateVarMap( DdManager * dd, Aig_Man_t * p, int fVerbose )
{
    DdNode ** pbVarsX, ** pbVarsY;
    DdNode * bTemp, * bProd;
    Aig_Obj_t * pLatch;
    int i;

    // set the variable mapping for Cudd_bddVarMap()
    pbVarsX = ABC_ALLOC( DdNode *, dd->size );
    pbVarsY = ABC_ALLOC( DdNode *, dd->size );
    bProd = (dd)->one;         Cudd_Ref( bProd );
    Saig_ManForEachLo( p, pLatch, i )
    {
        pbVarsX[i] = dd->vars[ Saig_ManPiNum(p) + i ];
        pbVarsY[i] = dd->vars[ Saig_ManCiNum(p) + i ];
        // get the initial value of the latch
        bProd = Cudd_bddAnd( dd, bTemp = bProd, Cudd_Not(pbVarsX[i]) );      Cudd_Ref( bProd );
        Cudd_RecursiveDeref( dd, bTemp ); 
    }
    Cudd_SetVarMap( dd, pbVarsX, pbVarsY, Saig_ManRegNum(p) );
    ABC_FREE( pbVarsX );
    ABC_FREE( pbVarsY );

    Cudd_Deref( bProd );
    return bProd;
}

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

  Synopsis    [Collects the array of output BDDs.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
DdNode ** Aig_ManCreateOutputs( DdManager * dd, Aig_Man_t * p )
{
    DdNode ** pbOutputs;
    Aig_Obj_t * pNode;
    int i;
    // compute the transition relation
    pbOutputs = ABC_ALLOC( DdNode *, Saig_ManPoNum(p) );
    Saig_ManForEachPo( p, pNode, i )
    {
        pbOutputs[i] = Aig_ObjGlobalBdd(pNode);  Cudd_Ref( pbOutputs[i] );
    }
    return pbOutputs;
}

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

  Synopsis    [Collects the array of partition BDDs.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
DdNode ** Aig_ManCreatePartitions( DdManager * dd, Aig_Man_t * p, int fReorder, int fVerbose )
{
    DdNode ** pbParts;
    DdNode * bVar;
    Aig_Obj_t * pNode;
    int i;

    // extand the BDD manager to represent NS variables
    assert( dd->size == Saig_ManCiNum(p) );
    Cudd_bddIthVar( dd, Saig_ManCiNum(p) + Saig_ManRegNum(p) - 1 );

    // enable reordering
    if ( fReorder )
        Cudd_AutodynEnable( dd, CUDD_REORDER_SYMM_SIFT );
    else
        Cudd_AutodynDisable( dd );

    // compute the transition relation
    pbParts = ABC_ALLOC( DdNode *, Saig_ManRegNum(p) );
    Saig_ManForEachLi( p, pNode, i )
    {
        bVar  = Cudd_bddIthVar( dd, Saig_ManCiNum(p) + i );
        pbParts[i] = Cudd_bddXnor( dd, bVar, Aig_ObjGlobalBdd(pNode) );  Cudd_Ref( pbParts[i] );
    }
    // free global BDDs
    Aig_ManFreeGlobalBdds( p, dd );

    // reorder and disable reordering
    if ( fReorder )
    {
        if ( fVerbose )
            fprintf( stdout, "BDD nodes in the partitions before reordering %d.\n", Cudd_SharingSize(pbParts,Saig_ManRegNum(p)) );
        Cudd_ReduceHeap( dd, CUDD_REORDER_SYMM_SIFT, 100 );
        Cudd_AutodynDisable( dd );
        if ( fVerbose )
            fprintf( stdout, "BDD nodes in the partitions after reordering %d.\n", Cudd_SharingSize(pbParts,Saig_ManRegNum(p)) );
    }
    return pbParts;
}

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

  Synopsis    [Computes the set of unreachable states.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Aig_ManComputeReachable( DdManager * dd, Aig_Man_t * p, DdNode ** pbParts, DdNode * bInitial, DdNode ** pbOutputs, Saig_ParBbr_t * pPars, int fCheckOutputs )
{
    int fInternalReorder = 0;
    Bbr_ImageTree_t * pTree = NULL; // Suppress "might be used uninitialized"
    Bbr_ImageTree2_t * pTree2 = NULL; // Supprses "might be used uninitialized"
    DdNode * bReached, * bCubeCs;
    DdNode * bCurrent;
    DdNode * bNext = NULL; // Suppress "might be used uninitialized"
    DdNode * bTemp;
    Cudd_ReorderingType method;
    int i, nIters, nBddSize = 0, status;
    int nThreshold = 10000;
    abctime clk = Abc_Clock();
    Vec_Ptr_t * vOnionRings;
    int fixedPoint = 0;

    status = Cudd_ReorderingStatus( dd, &method );
    if ( status )
        Cudd_AutodynDisable( dd );

    // start the image computation
    bCubeCs  = Bbr_bddComputeRangeCube( dd, Saig_ManPiNum(p), Saig_ManCiNum(p) );    Cudd_Ref( bCubeCs );
    if ( pPars->fPartition )
        pTree = Bbr_bddImageStart( dd, bCubeCs, Saig_ManRegNum(p), pbParts, Saig_ManRegNum(p), dd->vars+Saig_ManCiNum(p), pPars->nBddMax, pPars->fVerbose );
    else
        pTree2 = Bbr_bddImageStart2( dd, bCubeCs, Saig_ManRegNum(p), pbParts, Saig_ManRegNum(p), dd->vars+Saig_ManCiNum(p), pPars->fVerbose );
    Cudd_RecursiveDeref( dd, bCubeCs );
    if ( pTree == NULL )
    {
        if ( !pPars->fSilent )
            printf( "BDDs blew up during qualitification scheduling.  " );
        return -1;
    }

    if ( status )
        Cudd_AutodynEnable( dd, method );

    // start the onion rings
    vOnionRings = Vec_PtrAlloc( 1000 );

    // perform reachability analysis
    bCurrent = bInitial;   Cudd_Ref( bCurrent );
    bReached = bInitial;   Cudd_Ref( bReached );
    Vec_PtrPush( vOnionRings, bCurrent );  Cudd_Ref( bCurrent );
    for ( nIters = 0; nIters < pPars->nIterMax; nIters++ )
    { 
        // check the runtime limit
        if ( pPars->TimeLimit && pPars->TimeLimit <= (Abc_Clock()-clk)/CLOCKS_PER_SEC )
        {
            printf( "Reached timeout after image computation (%d seconds).\n",  pPars->TimeLimit );
            Vec_PtrFree( vOnionRings );
            // undo the image tree
            if ( pPars->fPartition )
                Bbr_bddImageTreeDelete( pTree );
            else
                Bbr_bddImageTreeDelete2( pTree2 );
            pPars->iFrame = nIters - 1;
            return -1;
        }

        // compute the next states
        if ( pPars->fPartition )
            bNext = Bbr_bddImageCompute( pTree, bCurrent );           
        else
            bNext = Bbr_bddImageCompute2( pTree2, bCurrent );  
        if ( bNext == NULL )
        {
            if ( !pPars->fSilent )
                printf( "BDDs blew up during image computation.  " );
            if ( pPars->fPartition )
                Bbr_bddImageTreeDelete( pTree );
            else
                Bbr_bddImageTreeDelete2( pTree2 );
            Vec_PtrFree( vOnionRings );
            pPars->iFrame = nIters - 1;
            return -1;
        }
        Cudd_Ref( bNext );
        Cudd_RecursiveDeref( dd, bCurrent );

        // remap these states into the current state vars
        bNext = Cudd_bddVarMap( dd, bTemp = bNext );                    Cudd_Ref( bNext );
        Cudd_RecursiveDeref( dd, bTemp );
        // check if there are any new states
        if ( Cudd_bddLeq( dd, bNext, bReached ) ) {
            fixedPoint = 1;
            break;
        }
        // check the BDD size
        nBddSize = Cudd_DagSize(bNext);
        if ( nBddSize > pPars->nBddMax )
            break;
        // check the result
        for ( i = 0; i < Saig_ManPoNum(p); i++ )
        {
            if ( fCheckOutputs && !Cudd_bddLeq( dd, bNext, Cudd_Not(pbOutputs[i]) ) )
            {
                DdNode * bIntersect;
                bIntersect = Cudd_bddIntersect( dd, bNext, pbOutputs[i] );  Cudd_Ref( bIntersect );
                assert( p->pSeqModel == NULL );
                p->pSeqModel = Aig_ManVerifyUsingBddsCountExample( p, dd, pbParts, 
                    vOnionRings, bIntersect, i, pPars->fVerbose, pPars->fSilent ); 
                Cudd_RecursiveDeref( dd, bIntersect );
                if ( !pPars->fSilent )
                    Abc_Print( 1, "Output %d of miter \"%s\" was asserted in frame %d. ", i, p->pName, Vec_PtrSize(vOnionRings) );
                Cudd_RecursiveDeref( dd, bReached );
                bReached = NULL;
                pPars->iFrame = nIters;
                break;
            }
        }
        if ( i < Saig_ManPoNum(p) )
            break;
        // get the new states
        bCurrent = Cudd_bddAnd( dd, bNext, Cudd_Not(bReached) );        Cudd_Ref( bCurrent );
        Vec_PtrPush( vOnionRings, bCurrent );  Cudd_Ref( bCurrent );
        // minimize the new states with the reached states
//        bCurrent = Cudd_bddConstrain( dd, bTemp = bCurrent, Cudd_Not(bReached) ); Cudd_Ref( bCurrent );
//        Cudd_RecursiveDeref( dd, bTemp );
        // add to the reached states
        bReached = Cudd_bddOr( dd, bTemp = bReached, bNext );           Cudd_Ref( bReached );
        Cudd_RecursiveDeref( dd, bTemp );
        Cudd_RecursiveDeref( dd, bNext );
        if ( pPars->fVerbose )
            fprintf( stdout, "Frame = %3d. BDD = %5d. ", nIters, nBddSize );
        if ( fInternalReorder && pPars->fReorder && nBddSize > nThreshold )
        {
            if ( pPars->fVerbose )
                fprintf( stdout, "Reordering... Before = %5d. ", Cudd_DagSize(bReached) );
            Cudd_ReduceHeap( dd, CUDD_REORDER_SYMM_SIFT, 100 );
            Cudd_AutodynDisable( dd );
            if ( pPars->fVerbose )
                fprintf( stdout, "After = %5d.\r", Cudd_DagSize(bReached) );
            nThreshold *= 2;
        }
        if ( pPars->fVerbose )
//            fprintf( stdout, "\r" );
            fprintf( stdout, "\n" );

        if ( pPars->fVerbose )
        {
            double nMints = Cudd_CountMinterm(dd, bReached, Saig_ManRegNum(p) );
//            Extra_bddPrint( dd, bReached );printf( "\n" );
            fprintf( stdout, "Reachable states = %.0f. (Ratio = %.4f %%)\n", nMints, 100.0*nMints/pow(2.0, Saig_ManRegNum(p)) );
            fflush( stdout ); 
        }

    }
    Cudd_RecursiveDeref( dd, bNext );
    // free the onion rings
    Vec_PtrForEachEntry( DdNode *, vOnionRings, bTemp, i )
        Cudd_RecursiveDeref( dd, bTemp );
    Vec_PtrFree( vOnionRings );
    // undo the image tree
    if ( pPars->fPartition )
        Bbr_bddImageTreeDelete( pTree );
    else
        Bbr_bddImageTreeDelete2( pTree2 );
    if ( bReached == NULL )
        return 0; // proved reachable
    // report the stats
    if ( pPars->fVerbose )
    {
        double nMints = Cudd_CountMinterm(dd, bReached, Saig_ManRegNum(p) );
        if ( nIters > pPars->nIterMax || nBddSize > pPars->nBddMax )
            fprintf( stdout, "Reachability analysis is stopped after %d frames.\n", nIters );
        else
            fprintf( stdout, "Reachability analysis completed after %d frames.\n", nIters );
        fprintf( stdout, "Reachable states = %.0f. (Ratio = %.4f %%)\n", nMints, 100.0*nMints/pow(2.0, Saig_ManRegNum(p)) );
        fflush( stdout );
    }
//ABC_PRB( dd, bReached );
    Cudd_RecursiveDeref( dd, bReached );
    // SPG
    //
    if ( fixedPoint ) {
      if ( !pPars->fSilent ) {
        printf( "The miter is proved unreachable after %d iterations.  ", nIters );
      }
      pPars->iFrame = nIters - 1;
      return 1;
    }
    assert(nIters >= pPars->nIterMax || nBddSize >= pPars->nBddMax);
    if ( !pPars->fSilent )
      printf( "Verified only for states reachable in %d frames.  ", nIters );
    pPars->iFrame = nIters - 1;
    return -1; // undecided
}

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

  Synopsis    [Performs reachability to see if any PO can be asserted.]

  Description []
               
  SideEffects []
 
  SeeAlso     []

***********************************************************************/
int Aig_ManVerifyUsingBdds_int( Aig_Man_t * p, Saig_ParBbr_t * pPars )
{
    int fCheckOutputs = !pPars->fSkipOutCheck;
    DdManager * dd;
    DdNode ** pbParts, ** pbOutputs;
    DdNode * bInitial, * bTemp;
    int RetValue, i;
    abctime clk = Abc_Clock();
    Vec_Ptr_t * vOnionRings;

    assert( Saig_ManRegNum(p) > 0 );

    // compute the global BDDs of the latches
    dd = Aig_ManComputeGlobalBdds( p, pPars->nBddMax, 1, pPars->fReorder, pPars->fVerbose );    
    if ( dd == NULL )
    {
        if ( !pPars->fSilent )
            printf( "The number of intermediate BDD nodes exceeded the limit (%d).\n", pPars->nBddMax );
        return -1;
    }
    if ( pPars->fVerbose )
        printf( "Shared BDD size is %6d nodes.\n", Cudd_ReadKeys(dd) - Cudd_ReadDead(dd) );

    // check the runtime limit
    if ( pPars->TimeLimit && pPars->TimeLimit <= (Abc_Clock()-clk)/CLOCKS_PER_SEC )
    {
        printf( "Reached timeout after constructing global BDDs (%d seconds).\n",  pPars->TimeLimit );
        Cudd_Quit( dd );
        return -1;
    }

    // start the onion rings
    vOnionRings = Vec_PtrAlloc( 1000 );

    // save outputs
    pbOutputs = Aig_ManCreateOutputs( dd, p );

    // create partitions
    pbParts = Aig_ManCreatePartitions( dd, p, pPars->fReorder, pPars->fVerbose );

    // create the initial state and the variable map
    bInitial  = Aig_ManInitStateVarMap( dd, p, pPars->fVerbose );  Cudd_Ref( bInitial );

    // set reordering
    if ( pPars->fReorderImage )
        Cudd_AutodynEnable( dd, CUDD_REORDER_SYMM_SIFT );

    // check the result
    RetValue = -1;
    for ( i = 0; i < Saig_ManPoNum(p); i++ )
    {
        if ( fCheckOutputs && !Cudd_bddLeq( dd, bInitial, Cudd_Not(pbOutputs[i]) ) )
        {
            DdNode * bIntersect;
            bIntersect = Cudd_bddIntersect( dd, bInitial, pbOutputs[i] );  Cudd_Ref( bIntersect );
            assert( p->pSeqModel == NULL );
            p->pSeqModel = Aig_ManVerifyUsingBddsCountExample( p, dd, pbParts, 
                vOnionRings, bIntersect, i, pPars->fVerbose, pPars->fSilent ); 
            Cudd_RecursiveDeref( dd, bIntersect );
            if ( !pPars->fSilent )
                Abc_Print( 1, "Output %d of miter \"%s\" was asserted in frame %d. ", i, p->pName, -1 );
            RetValue = 0;
            break;
        }
    }
    // free the onion rings
    Vec_PtrForEachEntry( DdNode *, vOnionRings, bTemp, i )
        Cudd_RecursiveDeref( dd, bTemp );
    Vec_PtrFree( vOnionRings );
    // explore reachable states
    if ( RetValue == -1 )
        RetValue = Aig_ManComputeReachable( dd, p, pbParts, bInitial, pbOutputs, pPars, fCheckOutputs ); 

    // cleanup
    Cudd_RecursiveDeref( dd, bInitial );
    for ( i = 0; i < Saig_ManRegNum(p); i++ )
        Cudd_RecursiveDeref( dd, pbParts[i] );
    ABC_FREE( pbParts );
    for ( i = 0; i < Saig_ManPoNum(p); i++ )
        Cudd_RecursiveDeref( dd, pbOutputs[i] );
    ABC_FREE( pbOutputs );
//    if ( RetValue == -1 )
        Cudd_Quit( dd );
//    else
//        Bbr_StopManager( dd );

    // report the runtime
    if ( !pPars->fSilent )
    {
    ABC_PRT( "Time", Abc_Clock() - clk );
    fflush( stdout );
    }
    return RetValue;
}

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

  Synopsis    [Performs reachability to see if any PO can be asserted.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Aig_ManVerifyUsingBdds( Aig_Man_t * pInit, Saig_ParBbr_t * pPars )
{
    Abc_Cex_t * pCexOld, * pCexNew;
    Aig_Man_t * p;
    Aig_Obj_t * pObj;
    Vec_Int_t * vInputMap;
    int i, k, Entry, iBitOld, iBitNew, RetValue;
//    pPars->fVerbose = 1;
    // check if there are PIs without fanout
    Saig_ManForEachPi( pInit, pObj, i )
        if ( Aig_ObjRefs(pObj) == 0 )
            break;
    if ( i == Saig_ManPiNum(pInit) )
        return Aig_ManVerifyUsingBdds_int( pInit, pPars );
    // create new AIG
    p = Aig_ManDupTrim( pInit );
    assert( Aig_ManCiNum(p) < Aig_ManCiNum(pInit) );
    assert( Aig_ManRegNum(p) == Aig_ManRegNum(pInit) );
    RetValue = Aig_ManVerifyUsingBdds_int( p, pPars );
    if ( RetValue != 0 )
    {
        Aig_ManStop( p );
        return RetValue;
    }
    // the problem is satisfiable - remap the pattern
    pCexOld = p->pSeqModel;
    assert( pCexOld != NULL );
    // create input map
    vInputMap = Vec_IntAlloc( Saig_ManPiNum(pInit) );
    Saig_ManForEachPi( pInit, pObj, i )
        if ( pObj->pData != NULL )
            Vec_IntPush( vInputMap, Aig_ObjCioId((Aig_Obj_t *)pObj->pData) );
        else
            Vec_IntPush( vInputMap, -1 );
    // create new pattern
    pCexNew = Abc_CexAlloc( Saig_ManRegNum(pInit), Saig_ManPiNum(pInit), pCexOld->iFrame+1 );
    pCexNew->iFrame = pCexOld->iFrame;
    pCexNew->iPo    = pCexOld->iPo;
    // copy the bit-data
    for ( iBitOld = 0; iBitOld < pCexOld->nRegs; iBitOld++ )
        if ( Abc_InfoHasBit( pCexOld->pData, iBitOld ) )
            Abc_InfoSetBit( pCexNew->pData, iBitOld );
    // copy the primary input data
    iBitNew = iBitOld;
    for ( i = 0; i <= pCexNew->iFrame; i++ )
    {
        Vec_IntForEachEntry( vInputMap, Entry, k )
        {
            if ( Entry == -1 )
                continue;
            if ( Abc_InfoHasBit( pCexOld->pData, iBitOld + Entry ) )
                Abc_InfoSetBit( pCexNew->pData, iBitNew + k );
        }
        iBitOld += Saig_ManPiNum(p);
        iBitNew += Saig_ManPiNum(pInit);
    }
    assert( iBitOld < iBitNew );
    assert( iBitOld == pCexOld->nBits );
    assert( iBitNew == pCexNew->nBits );
    Vec_IntFree( vInputMap );
    pInit->pSeqModel = pCexNew;
    Aig_ManStop( p );
    return 0;
}

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


ABC_NAMESPACE_IMPL_END