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

  FileName    [fretFlow.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Flow-based retiming package.]

  Synopsis    [Max-flow computation.]

  Author      [Aaron Hurst]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - January 1, 2008.]

  Revision    [$Id: fretFlow.c,v 1.00 2008/01/01 00:00:00 ahurst Exp $]

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

#include "abc.h"
#include "vec.h"
#include "fretime.h"

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

static void dfsfast_e_retreat( Abc_Obj_t *pObj );
static void dfsfast_r_retreat( Abc_Obj_t *pObj );

#define FDIST(xn, xe, yn, ye) (FDATA(xn)->xe##_dist == (FDATA(yn)->ye##_dist + 1))

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

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

  Synopsis    [Fast DFS.]

  Description [Uses sink-distance-histogram heuristic.  May not find all
               flow paths: this occurs in a small number of cases where
               the flow predecessor points to a non-adjacent node and
               the distance ordering is perturbed.]
               
  SideEffects []

  SeeAlso     []

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

void dfsfast_preorder( Abc_Ntk_t *pNtk ) {
  Abc_Obj_t *pObj, *pNext;
  Vec_Ptr_t *vTimeIn, *qn = Vec_PtrAlloc(Abc_NtkObjNum(pNtk));
  Vec_Int_t *qe = Vec_IntAlloc(Abc_NtkObjNum(pNtk));
  int i, j, d = 0, end;
  int qpos = 0;

  // create reverse timing edges for backward traversal
#if !defined(IGNORE_TIMING)
  if (maxDelayCon)
    Abc_NtkForEachObj( pNtk, pObj, i ) {
      Vec_PtrForEachEntry( FTIMEEDGES(pObj), pNext, j ) {
        vTimeIn = FDATA(pNext)->vTimeInEdges;
        if (!vTimeIn) {
          vTimeIn = FDATA(pNext)->vTimeInEdges = Vec_PtrAlloc(2);
        }
        Vec_PtrPush(vTimeIn, pObj);
      }
    }
#endif

  // clear histogram
  memset(Vec_IntArray(vSinkDistHist), 0, sizeof(int)*Vec_IntSize(vSinkDistHist));

  // seed queue : latches, PIOs, and blocks
  Abc_NtkForEachObj( pNtk, pObj, i )
    if (Abc_ObjIsPo(pObj) ||
        Abc_ObjIsLatch(pObj) || 
        (fIsForward && FTEST(pObj, BLOCK))) {
      Vec_PtrPush(qn, pObj);
      Vec_IntPush(qe, 'r');
      FDATA(pObj)->r_dist = 1;
    } else if (Abc_ObjIsPi(pObj) || 
               (!fIsForward && FTEST(pObj, BLOCK))) {
      Vec_PtrPush(qn, pObj);
      Vec_IntPush(qe, 'e');
      FDATA(pObj)->e_dist = 1;
    }

  // until queue is empty...
  while(qpos < Vec_PtrSize(qn)) {
    pObj = (Abc_Obj_t *)Vec_PtrEntry(qn, qpos);
    assert(pObj);
    end = Vec_IntEntry(qe, qpos);
    qpos++;
    
    if (end == 'r') {
      d = FDATA(pObj)->r_dist;

      // 1. structural edges
      if (fIsForward) {
        Abc_ObjForEachFanin( pObj, pNext, i )
          if (!FDATA(pNext)->e_dist) {
            FDATA(pNext)->e_dist = d+1;
            Vec_PtrPush(qn, pNext);
            Vec_IntPush(qe, 'e');
          }
      } else
        Abc_ObjForEachFanout( pObj, pNext, i )
          if (!FDATA(pNext)->e_dist) {
            FDATA(pNext)->e_dist = d+1;
            Vec_PtrPush(qn, pNext);
            Vec_IntPush(qe, 'e');
          }

      if (d == 1) continue;

      // 2. reverse edges (forward retiming only)
      if (fIsForward) {
        Abc_ObjForEachFanout( pObj, pNext, i )
          if (!FDATA(pNext)->r_dist && !Abc_ObjIsLatch(pNext)) {
            FDATA(pNext)->r_dist = d+1;
            Vec_PtrPush(qn, pNext);
            Vec_IntPush(qe, 'r');
          }          
      }

      // 3. timimg edges (reverse)
#if !defined(IGNORE_TIMING)
      if (maxDelayCon && FDATA(pObj)->vTimeInEdges)
        Vec_PtrForEachEntry( FDATA(pObj)->vTimeInEdges, pNext, i ) {
          if (!FDATA(pNext)->r_dist) {
            FDATA(pNext)->r_dist = d+1;
            Vec_PtrPush(qn, pNext);
            Vec_IntPush(qe, 'r');
          }
        }
#endif
      
    } else { // if 'e'
      if (Abc_ObjIsLatch(pObj)) continue;

      d = FDATA(pObj)->e_dist;

      // 1. through node
      if (!FDATA(pObj)->r_dist) {
        FDATA(pObj)->r_dist = d+1;
        Vec_PtrPush(qn, pObj);
        Vec_IntPush(qe, 'r');
      }

      // 2. reverse edges (backward retiming only)
      if (!fIsForward) {
        Abc_ObjForEachFanin( pObj, pNext, i )
          if (!FDATA(pNext)->e_dist && !Abc_ObjIsLatch(pNext)) {
            FDATA(pNext)->e_dist = d+1;
            Vec_PtrPush(qn, pNext);
            Vec_IntPush(qe, 'e');
          }  
      }
    }
  }
 
  // create reverse timing edges for backward traversal
#if !defined(IGNORE_TIMING)
  if (maxDelayCon)
    Abc_NtkForEachObj( pNtk, pObj, i ) {
      vTimeIn = FDATA(pObj)->vTimeInEdges;
      if (vTimeIn) {
        Vec_PtrFree(vTimeIn);
        FDATA(pObj)->vTimeInEdges = 0;
      }
    }
#endif

  Abc_NtkForEachObj( pNtk, pObj, i ) {
    Vec_IntAddToEntry(vSinkDistHist, FDATA(pObj)->r_dist, 1);
    Vec_IntAddToEntry(vSinkDistHist, FDATA(pObj)->e_dist, 1);

#ifdef DEBUG_PREORDER
    printf("node %d\t: r=%d\te=%d\n", Abc_ObjId(pObj), FDATA(pObj)->r_dist, FDATA(pObj)->e_dist);
#endif
  }

  printf("\t\tpre-ordered (max depth=%d)\n", d+1);

  // deallocate
  Vec_PtrFree( qn );
  Vec_IntFree( qe );
}

int dfsfast_e( Abc_Obj_t *pObj, Abc_Obj_t *pPred ) {
  int i;
  Abc_Obj_t *pNext;
  
  if (fSinkDistTerminate) return 0;

  if(FTEST(pObj, BLOCK) ||
     Abc_ObjIsPi(pObj)) {
    assert(!fIsForward);
    return 1;
  }

  FSET(pObj, VISITED_E);

#ifdef DEBUG_VISITED
  printf("(%de=%d) ", Abc_ObjId(pObj), FDATA(pObj)->e_dist);
#endif  

  // 1. structural edges
  if (fIsForward)
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          FDIST(pObj, e, pNext, r) &&
          dfsfast_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }
  else
    Abc_ObjForEachFanin( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          FDIST(pObj, e, pNext, r) &&
          dfsfast_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }  
  
  if (Abc_ObjIsLatch(pObj))
    goto not_found;

  // 2. reverse edges (backward retiming only)
  if (!fIsForward) { 
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_E) &&
          FDIST(pObj, e, pNext, e) &&
          dfsfast_e(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("i");
#endif
        goto found;
      }
    }
  }
  
  // unwind
  if (FTEST(pObj, FLOW) && 
      !FTEST(pObj, VISITED_R) &&
      FDIST(pObj, e, pObj, r) &&
      dfsfast_r(pObj, FGETPRED(pObj))) {

    FUNSET(pObj, FLOW);
    FSETPRED(pObj, NULL);
#ifdef DEBUG_PRINT_FLOWS
    printf("u");
#endif
    goto found;
  }
  
 not_found:
  FUNSET(pObj, VISITED_E);
  dfsfast_e_retreat(pObj);
  return 0;
  
 found:
#ifdef DEBUG_PRINT_FLOWS
  printf("%d ", Abc_ObjId(pObj));
#endif 
  FUNSET(pObj, VISITED_E);
  return 1;
}

int dfsfast_r( Abc_Obj_t *pObj, Abc_Obj_t *pPred ) {
  int i;
  Abc_Obj_t *pNext, *pOldPred;

  if (fSinkDistTerminate) return 0;

#ifdef DEBUG_VISITED
  printf("(%dr=%d) ", Abc_ObjId(pObj), FDATA(pObj)->r_dist);
#endif  

  // have we reached the sink?
  if (Abc_ObjIsLatch(pObj) ||
      Abc_ObjIsPo(pObj) || 
      (fIsForward && FTEST(pObj, BLOCK))) {
    assert(pPred);
    return 1;
  }

  FSET(pObj, VISITED_R);

  if (FTEST(pObj, FLOW)) {

    pOldPred = FGETPRED(pObj);
    if (pOldPred && 
        !FTEST(pOldPred, VISITED_E) &&
        FDIST(pObj, r, pOldPred, e) &&
        dfsfast_e(pOldPred, pOldPred)) {

      FSETPRED(pObj, pPred);

#ifdef DEBUG_PRINT_FLOWS
      printf("fr");
#endif
      goto found;
    }
    
  } else {

    if (!FTEST(pObj, VISITED_E) &&
        FDIST(pObj, r, pObj, e) &&
        dfsfast_e(pObj, pObj)) {

      FSET(pObj, FLOW);
      FSETPRED(pObj, pPred);

#ifdef DEBUG_PRINT_FLOWS
      printf("f");
#endif
      goto found;
    }
  }
 
  // 2. reverse edges (forward retiming only)
  if (fIsForward) {
    Abc_ObjForEachFanin( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          FDIST(pObj, r, pNext, r) &&
          !Abc_ObjIsLatch(pNext) &&
          dfsfast_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("i");
#endif
        goto found;
      }
    }
  }
  
  // 3. timing edges
#if !defined(IGNORE_TIMING)
  if (maxDelayCon) 
    Vec_PtrForEachEntry( FTIMEEDGES(pObj), pNext, i) {
      if (!FTEST(pNext, VISITED_R) &&
          FDIST(pObj, r, pNext, r) &&
          dfsfast_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }
#endif
  
  FUNSET(pObj, VISITED_R);
  dfsfast_r_retreat(pObj);
  return 0;

 found:
#ifdef DEBUG_PRINT_FLOWS
  printf("%d ", Abc_ObjId(pObj));
#endif
  FUNSET(pObj, VISITED_R);
  return 1;
}

void
dfsfast_e_retreat(Abc_Obj_t *pObj) {
  Abc_Obj_t *pNext;
  int i, *h;
  int old_dist = FDATA(pObj)->e_dist;
  int adj_dist, min_dist = MAX_DIST;
  
  // 1. structural edges
  if (fIsForward)
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      adj_dist = FDATA(pNext)->r_dist;
      if (adj_dist) min_dist = MIN(min_dist, adj_dist);
    }
  else
    Abc_ObjForEachFanin( pObj, pNext, i ) {
      adj_dist = FDATA(pNext)->r_dist;
      if (adj_dist) min_dist = MIN(min_dist, adj_dist);
    }

  if (Abc_ObjIsLatch(pObj)) goto update;

  // 2. through
  if (FTEST(pObj, FLOW)) {
    adj_dist = FDATA(pObj)->r_dist;
    if (adj_dist) min_dist = MIN(min_dist, adj_dist);
  }

  // 3. reverse edges (backward retiming only)
  if (!fIsForward) {
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      adj_dist = FDATA(pNext)->e_dist;
      if (adj_dist) min_dist = MIN(min_dist, adj_dist);
    }
  }

 update:
  ++min_dist;
  if (min_dist >= MAX_DIST) min_dist = 0;
  // printf("[%de=%d->%d] ", Abc_ObjId(pObj), old_dist, min_dist+1);
  FDATA(pObj)->e_dist = min_dist;

  assert(min_dist < Vec_IntSize(vSinkDistHist));
  h = Vec_IntArray(vSinkDistHist);
  h[old_dist]--;
  h[min_dist]++;
  if (!h[old_dist]) {
    fSinkDistTerminate = 1;
  }
}

void
dfsfast_r_retreat(Abc_Obj_t *pObj) {
  Abc_Obj_t *pNext;
  int i, *h;
  int old_dist = FDATA(pObj)->r_dist;
  int adj_dist, min_dist = MAX_DIST;
  
  // 1. through or pred
  if (FTEST(pObj, FLOW)) {
    if (FGETPRED(pObj)) {
      adj_dist = FDATA(FGETPRED(pObj))->e_dist;
      if (adj_dist) min_dist = MIN(min_dist, adj_dist);
    }
  } else {
    adj_dist = FDATA(pObj)->e_dist;
    if (adj_dist) min_dist = MIN(min_dist, adj_dist);
  }

  // 2. reverse edges (forward retiming only)
  if (fIsForward) {
    Abc_ObjForEachFanin( pObj, pNext, i )
      if (!Abc_ObjIsLatch(pNext)) {
        adj_dist = FDATA(pNext)->r_dist;
        if (adj_dist) min_dist = MIN(min_dist, adj_dist);
      }
  }

  // 3. timing edges
#if !defined(IGNORE_TIMING)
  if (maxDelayCon) 
    Vec_PtrForEachEntry( FTIMEEDGES(pObj), pNext, i) {
      adj_dist = FDATA(pNext)->r_dist;
      if (adj_dist) min_dist = MIN(min_dist, adj_dist);
    }
#endif

  ++min_dist;
  if (min_dist >= MAX_DIST) min_dist = 0;
  //printf("[%dr=%d->%d] ", Abc_ObjId(pObj), old_dist, min_dist+1);
  FDATA(pObj)->r_dist = min_dist;

  assert(min_dist < Vec_IntSize(vSinkDistHist));
  h = Vec_IntArray(vSinkDistHist);
  h[old_dist]--;
  h[min_dist]++;
  if (!h[old_dist]) {
    fSinkDistTerminate = 1;
  }
}

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

  Synopsis    [Plain DFS.]

  Description [Does not use sink-distance-histogram heuristic.]
               
  SideEffects []

  SeeAlso     []

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

int dfsplain_e( Abc_Obj_t *pObj, Abc_Obj_t *pPred ) {
  int i;
  Abc_Obj_t *pNext;
  
  if (FTEST(pObj, BLOCK) || Abc_ObjIsPi(pObj)) {
    assert(!fIsForward);
    return 1;
  }

  FSET(pObj, VISITED_E);
  
  // printf(" %de\n", Abc_ObjId(pObj));
  
  // 1. structural edges
  if (fIsForward)
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          dfsplain_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }
  else
    Abc_ObjForEachFanin( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          dfsplain_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }
  
  if (Abc_ObjIsLatch(pObj))
    return 0;

  // 2. follow reverse edges
  if (!fIsForward) { // reverse retiming only
    Abc_ObjForEachFanout( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_E) &&
          dfsplain_e(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("i");
#endif
        goto found;
      }
    }
  }
  
  // unwind
  if (FTEST(pObj, FLOW) && 
      !FTEST(pObj, VISITED_R) &&
      dfsplain_r(pObj, FGETPRED(pObj))) {
    FUNSET(pObj, FLOW);
    FSETPRED(pObj, NULL);
#ifdef DEBUG_PRINT_FLOWS
    printf("u");
#endif
    goto found;
  }
  
  return 0;
  
 found:
#ifdef DEBUG_PRINT_FLOWS
  printf("%d ", Abc_ObjId(pObj));
#endif

  return 1;
}

int dfsplain_r( Abc_Obj_t *pObj, Abc_Obj_t *pPred ) {
  int i;
  Abc_Obj_t *pNext, *pOldPred;

  // have we reached the sink?
  if (Abc_ObjIsLatch(pObj) || 
      Abc_ObjIsPo(pObj) || 
      (fIsForward && FTEST(pObj, BLOCK))) {
    assert(pPred);
    return 1;
  }

  FSET(pObj, VISITED_R);

  // printf(" %dr\n", Abc_ObjId(pObj));

  if (FTEST(pObj, FLOW)) {

    pOldPred = FGETPRED(pObj);
    if (pOldPred && 
        !FTEST(pOldPred, VISITED_E) &&
        dfsplain_e(pOldPred, pOldPred)) {

      FSETPRED(pObj, pPred);

#ifdef DEBUG_PRINT_FLOWS
      printf("fr");
#endif
      goto found;
    }
    
  } else {

    if (!FTEST(pObj, VISITED_E) &&
        dfsplain_e(pObj, pObj)) {

      FSET(pObj, FLOW);
      FSETPRED(pObj, pPred);

#ifdef DEBUG_PRINT_FLOWS
      printf("f");
#endif
      goto found;
    }
  }
 
  // 2. follow reverse edges
  if (fIsForward) { // forward retiming only
    Abc_ObjForEachFanin( pObj, pNext, i ) {
      if (!FTEST(pNext, VISITED_R) &&
          !Abc_ObjIsLatch(pNext) &&
          dfsplain_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("i");
#endif
        goto found;
      }
    }
  }
  
  // 3. follow timing constraints
#if !defined(IGNORE_TIMING)
  if (maxDelayCon) 
    Vec_PtrForEachEntry( FTIMEEDGES(pObj), pNext, i) {
      if (!FTEST(pNext, VISITED_R) &&
          dfsplain_r(pNext, pPred)) {
#ifdef DEBUG_PRINT_FLOWS
        printf("o");
#endif
        goto found;
      }
    }
#endif
  
  return 0;

 found:
#ifdef DEBUG_PRINT_FLOWS
  printf("%d ", Abc_ObjId(pObj));
#endif
  return 1;
}
