////////////////////////////////////////////////////////////////////
//  This implements a massive FIFO of size ONE_FRAME_SPAN
//  in the off-chip DDR2 memory. 
//  The FIFO is currently read and written one row at a time
//  after an initial frame is buffered.
////////////////////////////////////////////////////////////////////


#include <io.h>
#include <system.h>
#include <string.h>
#include <stdio.h>
#include "sys/alt_flash.h"
#include "altera_avalon_pio_regs.h"
#include "alt_types.h"

// What the values should be:
//#define ONE_FRAME_SPAN 1536000          // 800*480*32/8
//#define ONE_ROW_SPAN 3200               // 800*32/8
// Debugging constants:
#define ONE_FRAME_SPAN 32
#define ONE_ROW_SPAN 16
//#define OUTPUT_FIFO_SIZE 16000

/******************************************************************
*  Function: RWDDRTransfer
*   - This implements the communication with the off-chip DDR2.  
* 
******************************************************************/

int RWDDRTransfer(unsigned long ddr_offset_passed,
                  unsigned int rw  )
{
   //unsigned int j = 0; 
   static unsigned long ddr_offset;
   static int wait_state;
   static unsigned long debug_reg1; 
   static unsigned long debug_reg2; 
   static unsigned long debug_reg3; 
   static unsigned long debug_reg4; 
  
   ddr_offset =  ddr_offset_passed;
   
   static unsigned long *DDR_address = (unsigned long *) 0x08000200; //long is 32 bits.
   
   //Debug//
   
   // There will be a fatal error if DDR memory is read from before it has been written to, which this proved. 
 /* DDR_address[0x0] = 0x1;
   DDR_address[0x4] = 0x2;
   DDR_address[0x8] = 0x3;
   DDR_address[0xC] = 0x4;
    debug_reg1 = DDR_address[0x0];
    debug_reg2 = DDR_address[0x4];
    debug_reg3 = DDR_address[0x8];
    debug_reg4 = DDR_address[0xC];
   */
   ////////////////////////////////////
   
   // Write to ddr 
   // The controller can only buffer four requests at a time.
   if(!rw)
   {         
       DDR_address[ddr_offset] = IORD_ALTERA_AVALON_PIO_DATA(PIXEL_IN1_BASE);
       DDR_address[ddr_offset + 0x4] = IORD_ALTERA_AVALON_PIO_DATA(PIXEL_IN2_BASE);
       DDR_address[ddr_offset + 0x8] = IORD_ALTERA_AVALON_PIO_DATA(PIXEL_IN3_BASE);
       DDR_address[ddr_offset + 0xC] = IORD_ALTERA_AVALON_PIO_DATA(PIXEL_IN4_BASE);  
    }
    
   
   // Read from ddr
   else if(rw)
   {
        IOWR_ALTERA_AVALON_PIO_DATA(PIXEL_OUT1_BASE, DDR_address[ddr_offset]);
        IOWR_ALTERA_AVALON_PIO_DATA(PIXEL_OUT2_BASE, DDR_address[ddr_offset + 0x4]);
        IOWR_ALTERA_AVALON_PIO_DATA(PIXEL_OUT3_BASE, DDR_address[ddr_offset + 0x8]);
        IOWR_ALTERA_AVALON_PIO_DATA(PIXEL_OUT4_BASE, DDR_address[ddr_offset + 0xC]);
   }
    
    return 0;
}


/******************************************************************
*  Function: MemInterfaceControlTop
*
*  This implements a massive FIFO of size ONE_FRAME_SPAN
*  in the off-chip DDR2 memory. 
*  The FIFO is currently read and written one row at a time
*  after an initial frame is buffered.  
******************************************************************/
int main(void)
{
    static unsigned long ddr_offset;   // Read offset
    static unsigned int rw_flag = 0;   // Starts by writing to the ddr. 
    static unsigned int init_flag = 1; // Upon reset, it will buffer an entire frame. 
    static unsigned long prev_offset = 0; 
      
    //ddr_offset = (alt_u32*) malloc(4);
    // Initial control register values on reset.
    IOWR_ALTERA_AVALON_PIO_DATA(DDR_WR_READY_BASE, 0);
   
    ddr_offset = 0x0;
 
    while (1)
  {      
        if (!rw_flag)
        {
            // First trigger the ddr_wr_ready flag to make a data block available from the hardware.
            IOWR_ALTERA_AVALON_PIO_DATA(DDR_WR_READY_BASE, 1);
            IOWR_ALTERA_AVALON_PIO_DATA(DDR_WR_READY_BASE, 0);  // Until next loop.
            // Write the block to the ddr2 off-chip memory.
            RWDDRTransfer(ddr_offset, 0);
            ddr_offset = ddr_offset + 16;      
            // Once an initial frame or a row is buffered trigger the rw flag. 
            if ((init_flag) && (ddr_offset == ONE_FRAME_SPAN))
            {
                init_flag = 0;
                rw_flag = 1;
                ddr_offset = 0x0;
                prev_offset = 0x0;
            }
            else if ((!init_flag) && (ddr_offset == prev_offset))
            {
               rw_flag = 1;
               if (ddr_offset == ONE_FRAME_SPAN)   // On the last write, reset the offset pointer.
               {
                    ddr_offset = 0x0;
                    prev_offset = 0x0;
               }
            } 
        }   
        
        
        else if (rw_flag && (IORD_ALTERA_AVALON_PIO_DATA(NIOS_RDREQ_BASE))) //&& 
                    //(IORD_ALTERA_AVALON_PIO_DATA(OUTPUT_WRUSED_BASE) < OUTPUT_FIFO_SIZE))
        {
            // Write to the hardware.  The interface to the hardware generates a new data flag. 
            RWDDRTransfer(ddr_offset, 1);
            ddr_offset = ddr_offset + 16;
            // Trigger the new_pixel flag so the hardware can capture the data_outs.
            IOWR_ALTERA_AVALON_PIO_DATA(NEW_PIXEL_BASE, 1);
            IOWR_ALTERA_AVALON_PIO_DATA(NEW_PIXEL_BASE, 0);
            // When a row has been read go back to the write state. 
            if (ddr_offset == (ONE_ROW_SPAN + prev_offset))
            {
                rw_flag = 0;
                prev_offset = ddr_offset; // Save the current address. 
                // Move the offset so that it will write to addresses that were just read from. 
                ddr_offset = ddr_offset - ONE_ROW_SPAN; 
            }
        }   
        
  } 
         
  

}
