/*

Freely Distributable C30 Simulator Package

Copyright (c) 1996-1998 The University of Texas
All Rights Reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
The GNU Public License is available in the file LICENSE, or you
can write to the Free Software Foundation, Inc., 59 Temple Place -
Suite 330, Boston, MA 02111-1307, USA, or you can find it on the
World Wide Web at http://www.fsf.org.

Authors: Chi Duong, Brian Evans, and Chris Moy
Version: @(#)cycle.cc	1.33	01/19/98

Department of Electrical and Computer Engineering
The University of Texas, Austin, TX 78712-1084

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "state.h"
#include "pipeline.h"
#include "memmap.h"
#include "cycle.h"
#include "fetch.h"
#include "assm_fun.h"
#include "execute.h"
#include "opcodes.h"

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef __cpluscplus
#define inline
#endif


/*
Compares the program counter to each of the breakpoints.
if a match is found, the function returns a 1.
*/
static inline
int pcNotAtBreakpoint(uint32 pc, const uint32* breaks, int breakCount) {
  int i = 0;
  while (i++ < breakCount) {
    if (pc == *breaks++) return 0;
  }
  return 1;
}
/* delay the changes of address registers during decode stage to
   execution/write-back stage */
static inline
void delayChangesFromDecode2Execute(state * st) {
  if (st->updateArn[0].flag==0) {
    int i;
    for (i = 0; i<2; i++) {
      if (st->updateArn[0].arnAddr[i] != 0) {
        *(st->updateArn[0].arnAddr[i]) = st->updateArn[0].arnVal[i];
        st->updateArn[0].arnAddr[i] = 0;
      }
    }
  }
  else if (st->updateArn[1].flag==0) {
    int i;
    for (i = 0; i < 2; i++) {
      if (st->updateArn[1].arnAddr[i] != 0){
        *(st->updateArn[1].arnAddr[i]) = st->updateArn[1].arnVal[i];
        st->updateArn[1].arnAddr[i] = 0;
      }
    }
  }

  int i;
  for (i = 0; i < 2; i++) {
    if (st->updateArn[i].flag != -1) {
      (st->updateArn[i].flag)--;
    }
  }
  if ((st->updateArn[0].flag) == -1) {
    st->saveArn = 0;
  }
  // keep track with st->cond
  for (i = 0; i < 2; i++) {
    if (st->cond[i].flag != -1)
      (st->cond[i].flag)--;
  }
}

/* update st->rc from pipe->rc and pipe->rci */
static inline
void transferRCFromPipeline2State(state * st, pipeline * pipe) {
  if(pipe->repeatModeModifyRC){
  		st->rc = pipe->rci[0];
  		pipe->rci[0] = pipe->rci[1];
  		pipe->rci[1] = pipe->rc;
      if ((int32)st->rc==-1)
      	pipe->repeatModeModifyRC = 0;
  }
  else if(pipe->rc != st->rc)
  		pipe->rc = pipe->rci[0] = pipe->rci[1] = st->rc;
}
/*
Flush the pipeline, and set all program counters to pipe->pc.
*/
void C30SimFlushPipeline(pipeline* pipe, state* st) {
    pipe->finalOpcode1 = S_nop;
    pipe->finalDest1 = 0;
    pipe->finalDest2 = 0;
    pipe->opcode1 = S_nop;                         /* clear everything out */
    pipe->dest1 = pipe->dest2 = 0;
    pipe->inst = C30_NOP_INST;
    C30SimUpdateProgramCounter(pipe, st, pipe->pc);
    pipe->source1 = pipe->source2 = pipe->source3 = pipe->source4 = &pipe->hold;
    pipe->rc = pipe->rci[0] = pipe->rci[1] = st->rc;
    st->updateArn[0].flag = st->updateArn[1].flag = -1;
    st->updateArn[0].arnAddr[0] = st->updateArn[0].arnAddr[1] =
    st->updateArn[1].arnAddr[0] = st->updateArn[1].arnAddr[1] = 0;
    st->ar0_s = st->ar0;
    st->ar1_s = st->ar1;
    st->ar2_s = st->ar2;
    st->ar3_s = st->ar3;
    st->ar4_s = st->ar4;
    st->ar5_s = st->ar5;
    st->ar6_s = st->ar6;
    st->ar7_s = st->ar7;
    /* reset register-access counters which may be set in decode stage
    of the instructions being flushed */
    pipe->accessAR = pipe->accessDP = pipe->accessSP = 0;
    pipe->tempAccessAR = pipe->tempAccessDP = pipe->tempAccessSP = 0;
}



/*
Run one c30 cycle. It initializes the cycle first.  Then it executes the
four stages of the pipeline while checking for pipeline conflicts and
interlocking accordingly.  It returns 1 on successful execution, and
returns 0 if the program counter points to an illegal address.
*/                                         
int C30SimCycle(state *st, pipeline *pipe) {
  /* Check for a valid setting on the program counter */
  int ret = (C30SimMemMap(st,pipe->pc) != &st->dummy);

  /* character string for disassembled instrictions */
  char buffer[128];
  buffer[0] = 0;

  /* reset memory accesses */
  pipe->access0 = 0;
  pipe->access1 = 0;
  pipe->accessROM = 0;
  /* check register accesses and reset interlock flag if not in interlock condition */
  if((pipe->accessAR<2) && (pipe->accessDP<2) && (pipe->accessSP<2))
    	pipe->interlock = FALSE;
  pipe->flushed = 0;

  if (pipe->interlock) {
    strcpy(buffer, "NOP ; interlock"); }
  else {
    /* increment memory access counters for read and writeback stage */
    C30SimMemAccess(pipe->finalDest1, st, pipe);
    uint32 * finalDest1 = pipe->finalDest1;
    C30SimMemAccess(pipe->finalDest2, st, pipe);  /* prepare dest for write back */
    uint32 * finalDest2 = pipe->finalDest2;

    pipe->finalOpcode1(pipe, st);              /* execute current instruction */
    delayChangesFromDecode2Execute(st);

    C30SimMemAccess(pipe->source1, st, pipe);     /* get ready for read stage */
    uint32 * source1 = pipe->source1;
    C30SimMemAccess(pipe->source2, st, pipe);
    uint32 * source2 = pipe->source2;
    C30SimMemAccess(pipe->source3, st, pipe);
    uint32 * source3 = pipe->source3;
    C30SimMemAccess(pipe->source4, st, pipe);
    uint32 * source4 = pipe->source4;

    /* if interlock occurs in read stage then no operation on next execution */
    if (pipe->interlock) {
      strcpy(buffer, "NOP ; interlock");
      pipe->finalOpcode1 = S_nop;
      pipe->finalDest1 = 0;
      pipe->finalDest2 = 0;
    }

    /* if an instruction flushes the pipeline ...,
    pipeline->flushed is set in execution stage */
    else if (pipe->flushed) {
      strcpy(buffer, "NOP ; flushed");
      C30SimFlushPipeline(pipe, st);
    }

    else {
  	   transferRCFromPipeline2State(st, pipe);
      int decodeRet = 0;                        /* decode stage return value */
      C30SimRead(pipe,st);                     /* execute operand read stage */
      if ((decodeRet = Disasm(pipe->inst,buffer,pipe,st))) { /* decode stage */
        /* no operation if opcode not found or interlock */
        pipe->opcode1 = S_nop;
        pipe->source1 = pipe->source2 = pipe->source3 =
                        pipe->source4 = &pipe->hold;
        pipe->dest1 = pipe->dest2 = 0;

        if (decodeRet == 2) {
          pipe->interlock = TRUE;     /* ignore disassembled instr in buffer */
          st->DSKdebStep = 1;
          strcpy(buffer, "NOP ; interlock");
        }
        else {
          strcpy(buffer, "NOP ; opcode not found");
        }
      }
      else { /* update register access counters */
      	pipe->accessAR = pipe->tempAccessAR;
         pipe->accessDP = pipe->tempAccessDP;
         pipe->accessSP = pipe->tempAccessSP;
      }
      /* Make sure that the PC points to a valid C30 address */
      uint32* pcAddress = C30SimMemMap(st,pipe->pc);
      if (pcAddress == &st->dummy) ret = 0;
      if (C30SimMemAccess(pcAddress, st, pipe) == 2)
        pipe->inst = C30_NOP_INST;
      else if (!pipe->interlock)
        C30SimFetch(st, pipe);                                 /* fetch stage */
    }
    /* decrement the register access counter for read and writeback stage */
    if(finalDest1)
    	C30SimRegAccess(finalDest1, st, pipe);
    if(finalDest2)
  	 	C30SimRegAccess(finalDest2, st, pipe);
    if(source1)
  	 	C30SimRegAccess(source1, st, pipe);
    if(source2)
  	 	C30SimRegAccess(source2, st, pipe);
    if(source3)
  	 	C30SimRegAccess(source3, st, pipe);
    if(source4)
  	 	C30SimRegAccess(source4, st, pipe);
  }

  /* print disassembled instruction */
  if (pipe->dasmPrint) {
    puts(buffer);
    fflush(stdout);
  }

  /* alert user to interlock */
  if (pipe->interlock) {
    /* this is not to tell the user where/what instruction caused interlock */
    /* but just tell the user when next instruction is not fetched due to interlock */
  	 /* ANSI C/C++ compilers will concatenate strings separate by white space */
    fprintf(stderr,
            "Warning: interlock, stall the fetch instruction of pc="
            C30_MEMORY_PRINTF
            "\n",
            pipe->pc);
    fflush(stderr);
  }

  return ret;
}

/*
The run routine gets passed the number of steps to execute as well as
the breakpoints and maximum number of breakpoints. It then calls the
cycle routine, keeps track of cycles executed, and exits when the
specified number of cycles have been executed or when the program reaches
a breakpoint. If the steps to be run is -1, then the program runs until
a breakpoint is reached.  This routine returns 0 if the run stopped
because the program counter assumed an invalid value, and non-zero
otherwise.  A return value of C30SIM_RUN_STEPS means that the simulation
stopped after the number of finite cycles requested completed.  A return
value of C30SIM_RUN_BREAKPOINT means the simulation stopped at a breakpoint.
*/
int C30SimRun(state *st, pipeline *pipe, int steps,
              const uint32* breaks, int breakCount) {
  int ret = C30SIM_RUN_BREAKPOINT;

  /* run forever: eliminate as much overhead as possible for speed */
  if (steps < 0) {
    if (breakCount == 0) {
      while (C30SimCycle(st, pipe)) {
         while((st->outputMode==DSK_DEBUGGER) && st->DSKdebStep) {
           st->DSKdebStep--;
           pipe->cycleCount++;
           C30SimCycle(st, pipe);
         }
         pipe->cycleCount++;
      }
      ret = C30SIM_RUN_BAD_PC;
    }
    else if (breakCount == 1) {
      uint32 theBreakpoint = *breaks;
      while ((pipe->pc != theBreakpoint) && C30SimCycle(st, pipe)) {
        while((st->outputMode==DSK_DEBUGGER) && st->DSKdebStep) {
          st->DSKdebStep--;
          pipe->cycleCount++;
          C30SimCycle(st, pipe);
        }
        pipe->cycleCount++;
      }
      ret = (pipe->pc != theBreakpoint) ?
              C30SIM_RUN_BAD_PC : C30SIM_RUN_BREAKPOINT;
    }
    else {
      while (pcNotAtBreakpoint(pipe->pc, breaks, breakCount) &&
             C30SimCycle(st, pipe)) {
       while((st->outputMode==DSK_DEBUGGER) && st->DSKdebStep) {
          st->DSKdebStep--;
          pipe->cycleCount++;
          C30SimCycle(st, pipe);
        }
        pipe->cycleCount++;
      }
      ret = pcNotAtBreakpoint(pipe->pc, breaks, breakCount) ?
              C30SIM_RUN_BAD_PC : C30SIM_RUN_BREAKPOINT;
    }
  }
  /* run for a specific number of cycles: speed is not critical as */
  /* the user is likely running this interactively */
  else {
    while (steps && pcNotAtBreakpoint(pipe->pc, breaks, breakCount) &&
           C30SimCycle(st, pipe)) {
     while((st->outputMode==DSK_DEBUGGER) && st->DSKdebStep) {
       st->DSKdebStep--;
       pipe->cycleCount++;
       C30SimCycle(st, pipe);
     }
     pipe->cycleCount++;
     steps--;
    }
    if (steps <= 0) {
      ret = C30SIM_RUN_STEPS;
    }
    else {
      ret = pcNotAtBreakpoint(pipe->pc, breaks, breakCount) ?
            C30SIM_RUN_BAD_PC : C30SIM_RUN_BREAKPOINT;
    }
  }

  return ret;
}
