/*
 * Linker Script Application
 *
 * contains global variables and a function that can be linked for different memory
 * targets.
 *
 * Symbols:
 *    VERBOSE   - when defined, time information for every cycle of each loop is displayed
 *    CORTEXA9  - required when running on the CORTEXA9 as this disables the caches
 *    CORTEXA53 - required when running on the CORTEXA53 as this disables the caches
 *
 * Notes:
 *    uses TTC3 with XTime functions
 *
 *
 * Future:
 *    add in interrupt handler and retarget in memory
 *    add in timer usage to measure performance improvement
 *
 * History:
 *    2020/02/26 - AM - 2019.2 - Updated macros for DDR memory in whereis function definition
 *    2019/11/19 - WK - 2019.1 - updated to use TTC3 with MPSoC devices (instead of AXI timer which requires PL)
 *    2018/02/06 - WK - issue found in 2017.3 editor preventing proper use of __ARM_ARCH to target processor. changed to permanently target A9
 *    2018/01/22 - BF/HW/WK - fixed timer issues
 *    2015/05/11 - WK - revised timer to work across platforms
 *    2015/03/13 - WK - initial cut
 *
 * ***************************************
 *
 * Solutions:
 *    for changing the "targetFunction" to be placed into the low latency memory region (place in the function prototypes area)
 *       int targetFunction(int x) __attribute__((section(".text_lowLatency")));
 *
 *    for changing global memory constants into data constant table (place in constant table area)
 *       int constTblWidth  __attribute__((section(".data_constTbl"))) = 3;
 *       int constTblLength __attribute__((section(".data_constTbl"))) = 2;
 *       int constTbl[2][3] __attribute__((section(".data_constTbl"))) = {{1,3,5}, {2,4,6}};
 *
 * ********************************************************************************************
 */

#include "stdio.h"
#include "xil_types.h"
#include "xstatus.h"
#include "stdlib.h"
#include "platform.h"
#include "xparameters.h"
#include "utils_print.h"
#include "xil_cache.h"

// #if (__ARM_ARCH == 4) - not working properly in 2019.1 despite CR filed against this
#include "xparameters_ps.h"
#include "xtime_l.h"

// definitions for managing test loops
#define NLOOPS_POWER     10
#define NLOOPS           1024
#define ITERATIONS_POWER 6
#define ITERATIONS       64
#define FULLCOUNT        4294967295 /* unsigned 32 bit counter - for MP/RF SoC devices - will probably never running into a wraparound situation, but just in case... */
                                    /* unsigned 16 bit counter - for Zynq7000's TTC */

// function prototypes
int targetFunction(int x);

// constant table
int constTblWidth = 3;
int constTblLength = 2;
int constTbl[2][3] = {{1,3,5}, {2,4,6}};

// helper function
void whereIs(char *varName, u64 addr);

/*
 * *** main function
 */
int main() {
   // general use local variables
   int i;               // used as generic index for looping
   u64 addrVar, addrFn; // used to locate items in memory

   // define local timer variables (32 bit timer)
   XTime start, end;
   u32 loop_overhead;
   u32 time_reading;
   u32 timerFunctionCallAvg = 0;

   // initialize the platform (which includes the caches)
   init_platform();
   use_only_nl();// the SDK terminal treats a \r as a \n. making this call tells the utils_print functions to only send a \n instead of a \n\r thus elminiating an unnecessary blank line

   // welcome the user
   writeStr_NL("\n\n\n***** Linker Script Demo *****");

   // knowing what the physical memory map is from the hdf file, call the helper function that
   // will tell us where one of the variables and our target function is hiding in memory
   writeStr_NL("Locating key variables and functions within memory...");
   addrVar = (u64)(&start);
   whereIs("variable \"start\"",addrVar);
   addrVar = (u64)(&constTblWidth);
   whereIs("variable \"constTblWidth\"",addrVar);
   addrFn = (u64)(&targetFunction);
   whereIs("function \"targetFunction\"",addrFn);
   
// disable the L1 & L2 data and instruction caches - this is done to make sure that the numbers that are
// being collected are actual accesses to DDR and not from the cache
#ifndef ENABLE_CACHES
 //  #if (__ARM_ARCH == 4)
 //    Xil_L1ICacheDisable();            // see UG647 - Section on A9 Processor Cache APIs
 //    Xil_L1DCacheDisable();
 //    Xil_L2ICacheDisable();
 //    Xil_L2DCacheDisable();
     Xil_DCacheDisable();              // see UG647 - Section on A53 64bit mode Processor Cache APIs
     Xil_ICacheDisable();
     writeStr_NL("caches disabled!");
 //  #endif
   #if (__ARM_ARCH == 8)
 //    Xil_DCacheDisable();              // see UG647 - Section on A53 64bit mode Processor Cache APIs
 //    Xil_ICacheDisable();
   #endif
   #ifdef __MICROBLAZE__
 //    Xil_DCacheDisable();                // see UG647 - Section on Microblaze Processor Cache APIs
 //    Xil_ICacheDisable();
   #endif
#endif
   
   int avgTargetFuncTime = 0;

   //XTime start, end;		 					   // each time measurement is made by capturing the current time before and after the process to measure
   XTime_StartTimer();                             // start the free-running TTC3 timer

   // measure how long it takes to call the time reading routine
   // the time to get into any given function is going to be about the same. the time reading function doesn't use an argument, so it will execute
   // a bit faster than a function with an argument (as the argument has to get pushed onto the stack). This may be a bit off depending where the stack
   // resides - in a fast (local) memory, or out in DDR. Keep this difference in mind as you work through the lab.
   writeStr("First, measure time to get in and out of the getTime() function by running "); writeInt(ITERATIONS); writeStr_NL(" iterations");
   for (i = 0; i < ITERATIONS; i++) {
      XTime_GetTime(&start);       			       // sample the time (looking at the time to call timer_getTime() which will approximate all function call overhead)
      XTime_GetTime(&end);   	                   // sample it again to see how long it took
      if (start < end) {
         time_reading = end - start;               // difference in time is the duration
      } else {
         writeStr_NL("wraparound when measuring time access overhead!");
         time_reading = FULLCOUNT - start + end;   // manage a wrap-around of the timer (otherwise time will appear to be negative)
      }
#ifdef VERBOSE
      writeInt(i+1); writeStr(": "); writeInt_NL(time_reading);
#endif
      timerFunctionCallAvg = timerFunctionCallAvg + time_reading;    // add this sample
   }
   writeStr("Average time to call getTime():  ");
   timerFunctionCallAvg = timerFunctionCallAvg >> ITERATIONS_POWER;  // compute the average time for the function call
   writeInt_NL(timerFunctionCallAvg);
   writeStr_NL(" ");

   //measure how long it takes to do a loop (that is, measure the time to increment, test, and jump)
   writeStr("Second, calculate the total loop overhead by sampling "); writeInt(NLOOPS);  writeStr_NL(" loops.");

   XTime_GetTime(&start);                        // sample the current time
   for (i = 0; i < NLOOPS; i++) {                  // optimization must be turned OFF otherwise this value goes to 0
      // do nothing here - just looping
   }
   XTime_GetTime(&end);
   if (start < end) {
      loop_overhead = end - start;              // difference in time is the duration
   } else {
      writeStr_NL("wraparound when measuring loop!");
      loop_overhead = FULLCOUNT - start + end;  // manage a wrap-around of the timer (otherwise time will appear to be negative)
   }  
   loop_overhead -= timerFunctionCallAvg; // remove the time it took to start the timer
   writeStr("The total loop overhead is ");   writeInt_NL(loop_overhead);
   loop_overhead = (loop_overhead + (NLOOPS >> 1)) >> NLOOPS_POWER; // time per loop - adjust numerator to give us effective rounding
   writeStr("Average loop overhead per iteration is ");  writeInt_NL(loop_overhead);  writeStr_NL(" ");

   // measure how long it takes to execute the target function
   writeStr_NL("Third, Measure how long it takes to execute the target function: ");
   for (i = 0; i < ITERATIONS; i++) {              // loop several times
	   XTime_GetTime(&start);                     // get the time sample
      end = targetFunction(i);                     // call the target function
      XTime_GetTime(&end);                       // get the ending time sample
      if (start < end) {
         time_reading = end - start - loop_overhead - timerFunctionCallAvg;// difference in time is the duration
      } else {
         writeStr_NL("wraparound when measuring target function access time!");
         time_reading = FULLCOUNT - start + end - loop_overhead - timerFunctionCallAvg; // manage a wrap-around of the timer (otherwise time will appear to be negative)
      }
#ifdef VERBOSE
      writeInt(i+1); writeStr(": "); writeInt_NL(time_reading);
#endif
      avgTargetFuncTime += time_reading;
   }
   writeStr("Average Target FunctionTime: ");
   writeInt_NL(avgTargetFuncTime/ITERATIONS);

   writeStr_NL("Done!");

   // do the interrupt driven stuff (future)
   while (0) {
   }

   cleanup_platform();
   return 0;
}

/*
 * targetFunction()
 */
int targetFunction(int x) {
   int r = x % 1;
   int c = x % 2;
   return (x + r + c + 1) * constTbl[r][c];
}

/*
 * helper function to indicate where the identified variable is located
 */
void whereIs(char *varName, u64 addr) {
   writeStr("location of variable \"");
   writeStr(varName);
   writeStr("\" in memory: ");
   intToHexString(addr);
   writeStr(" is in ");

   // based on the architecture, use the information from the mpp which is hard coded here to figure out where in memory we are
//   #if __ARM_ARCH == 4
//      if      ((addr >= 0x00000000) && (addr <= 0x0002FFFF)) { writeStr_NL("ps7_ram_0"); }
//      else if ((addr >= 0x00100000) && (addr <= 0x1FFFFFFF)) { writeStr_NL("ddr"); }
//      else                                                   { writeStr_NL("???"); }
//   #endif

   if      ((addr >= XPAR_PSU_OCM_RAM_0_S_AXI_BASEADDR) &&
		    (addr <= XPAR_PSU_OCM_RAM_0_S_AXI_HIGHADDR))                  {writeStr_NL("OCM RAM"); }
   else if ((addr >= XPAR_PSU_DDR_0_S_AXI_BASEADDR) &&
            (addr <= XPAR_PSU_DDR_0_S_AXI_HIGHADDR )) { writeStr_NL("DDR"); }
   else                                                                   { writeStr_NL("???"); }
   // future: add in microblaze and MPSoC memory map
}

