// ========================================================================================================
// ========================================================================================================
// ************************************************ Histo.c ***********************************************
// ========================================================================================================
// ========================================================================================================

#include "common.h"


// ===========================================================================================================
// ===========================================================================================================
// ===========================================================================================================
// C algorithm of the function carried out in the hardware

void ComputeHisto(int max_vals, int num_vals, short *vals, short LV_bound, short HV_bound, 
   short DIST_range, short precision_scaler, short *software_histo)
   {
   short LV_addr, HV_addr, LV_set, HV_set;
   int PN_num, bin_num, HISTO_ERR;
   short smallest_val;
   short dist_cnt_sum; 
   int dist_mean_sum;
   short temp_val;
   short range;

// Initialize variables.
   HISTO_ERR = 0;
   dist_mean_sum = 0;

// Clear out the counts in the distribution bins. 
   for ( bin_num = 0; bin_num < DIST_range; bin_num++ )
      software_histo[bin_num] = 0;

// Find smallest value. Then obtain the integer portion (low order 4 bits of the shorts are assumed to be part of the
// fractional component by the hardware -- fixed point floats).
   for ( PN_num = 0; PN_num < num_vals; PN_num++ ) 
      if ( PN_num == 0 )
         smallest_val = vals[PN_num];
      else if ( smallest_val > vals[PN_num] )
         smallest_val = vals[PN_num];
   smallest_val /= precision_scaler;

// Construct the histogram and compute the mean
   for ( PN_num = 0; PN_num < num_vals; PN_num++ ) 
      {

// Add current val to sum for mean calc.
      dist_mean_sum += (int)vals[PN_num];

// Adjust integer portion of vals by subtracting smallest value in the distribution. 
      temp_val = vals[PN_num]/precision_scaler - smallest_val;

//printf("%d) temp_val %d\n", PN_num, temp_val);

// Sanity check.
      if ( temp_val >= DIST_range )
         HISTO_ERR = 1;

      software_histo[temp_val]++; 
      }

// Sweep the histogram and record the address where the lower and higher bounds are exceeded.
   LV_addr = 0;
   HV_addr = 0;
   LV_set = 0;
   HV_set = 0;
   dist_cnt_sum = 0;
   for ( bin_num = 0; bin_num < DIST_range; bin_num++ ) 
      { 
      dist_cnt_sum += software_histo[bin_num]; 

// As soon as the is satisfied the first time, stop updating it.
      if ( LV_set == 0 && dist_cnt_sum >= LV_bound )
         {
         LV_addr = bin_num;
         LV_set = 1;
         }

// Keep updating until the bound is exceeded than stop.
      if ( dist_cnt_sum <= HV_bound )
         { 
         HV_addr = bin_num; 
         HV_set = 1;
         }
      }
   range = HV_addr - LV_addr + 1;

// Error check
   if ( LV_set == 0 || HV_set == 0 )
      HISTO_ERR = 1;

   if ( HISTO_ERR == 1 )
      printf("ERROR: ComputeHisto(): Histo error!\n"); 

   printf("Software Computed Stats: Smallest Val %d\tLV_addr %d\tHV_addr %d\tMean %.4f\tRange %d\n", 
      smallest_val, LV_addr, HV_addr, (float)(dist_mean_sum/num_vals)/precision_scaler, (int)range);
   fflush(stdout);

   return; 
   }


// ========================================================================================================
// ========================================================================================================
// Read integer data from a file and store it in an array.

int ReadData(int max_string_len, int max_data_vals, char *infile_name, short *data_arr_in)
   {
   char line[max_string_len], *char_ptr;
   float temp_float;
   FILE *INFILE;
   int val_num;

   if ( (INFILE = fopen(infile_name, "r")) == NULL )
      { printf("ERROR: ReadData(): Could not open %s\n", infile_name); fflush(stdout);  exit(EXIT_FAILURE); }

   val_num = 0;
   while ( fgets(line, max_string_len, INFILE) != NULL )
      {

// Find the newline and eliminate it.
      if ((char_ptr = strrchr(line, '\n')) != NULL)
         *char_ptr = '\0';

// Skip blank lines
      if ( strlen(line) == 0 )
         continue;

// Sanity check
      if ( val_num >= max_data_vals )
         { printf("ERROR: ReadData(): Exceeded maximum number of vals %d!\n", max_data_vals); fflush(stdout); exit(EXIT_FAILURE); }

// Read and convert value into an integer
      if ( sscanf(line, "%f", &temp_float) != 1 )
         { printf("ERROR: ReadData(): Failed to read an float value from file '%s'!\n", line); fflush(stdout); exit(EXIT_FAILURE); }

// Sanity check
      if ( (int)(temp_float*16) > MAX_SHORT_POS || (int)(temp_float*16) < MAX_SHORT_NEG )
         { printf("ERROR: ReadData(): Scaled float (by 16) larger than max or smaller than min value for short %d!\n", data_arr_in[val_num]); fflush(stdout); exit(EXIT_FAILURE); }

      data_arr_in[val_num] = (int)(temp_float*16);

      val_num++;
      }

   fclose(INFILE);

   return val_num;
   }


// ========================================================================================================
// ========================================================================================================
// Load the data from the data arry into the secure BRAM

void LoadUnloadBRAM(int max_string_len, int max_vals, int num_vals, int load_unload, short *IOData, 
   volatile unsigned int *CtrlRegA, volatile unsigned int *DataRegA, int ctrl_mask)
   {
   int val_num, locked_up;

   for ( val_num = 0; val_num < num_vals; val_num++ )
      {

// Sanity check
      if ( val_num >= max_vals )
         { printf("ERROR: LoadUnloadBRAM(): val_num %d greater than max_vals %d\n", val_num, max_vals); exit(EXIT_FAILURE); }

// Four step protocol
// 1) Wait for 'stopped' from hardware to be asserted
//printf("LoadUnloadBRAM(): Waiting 'stopped'\n"); fflush(stdout);
      locked_up = 0;
      while ( ((*DataRegA) & (1 << IN_SM_HANDSHAKE)) == 0 )
         {
         locked_up++;
         if ( locked_up > 10000000 )
            { 
            printf("ERROR: LoadUnloadBRAM(): 'stopped' has not been asserted for the threshold number of cycles -- Locked UP?\n"); 
            fflush(stdout); 
            locked_up = 0;
            }
         }

// 2) Put data into GPIO (load) or get data from GPIO (unload). Assert 'continue' for hardware
// Put the data bytes into the register and assert 'continue' (OUT_CP_HANDSHAKE).
//printf("LoadUnloadBRAM(): Reading/writing data and asserting 'continue'\n"); fflush(stdout);
      if ( load_unload == 0 )
         *CtrlRegA = ctrl_mask | (1 << OUT_CP_HANDSHAKE) | (0x0000FFFF & IOData[val_num]);

// When 'stopped' is asserted, the data is ready on the output register from the PNL BRAM -- get it.
      else
         {
         IOData[val_num] = (0x0000FFFF & *DataRegA);
         *CtrlRegA = ctrl_mask | (1 << OUT_CP_HANDSHAKE); 
         }

//printf("%d\tData value written or read %d\n", val_num, IOData[val_num]); fflush(stdout);

// 3) Wait for hardware to de-assert 'stopped' 
//printf("LoadUnloadBRAM(): Waiting de-assert of 'stopped'\n"); fflush(stdout);
      while ( ((*DataRegA) & (1 << IN_SM_HANDSHAKE)) != 0 );

// 4) De-assert 'continue'. ALSO, assert 'done' (OUT_CP_LM_ULM_DONE) SIMULTANEOUSLY if last word to inform hardware. 
//printf("LoadUnloadBRAM(): De-asserting 'continue' and possibly setting 'done'\n"); fflush(stdout);
      if ( val_num == num_vals - 1 )
         *CtrlRegA = ctrl_mask | (1 << OUT_CP_LM_ULM_DONE);
      else
         *CtrlRegA = ctrl_mask;
      }

// Handle case where 'num_vals' is 0.
   if ( num_vals == 0 )
      *CtrlRegA = ctrl_mask | (1 << OUT_CP_LM_ULM_DONE);

// De-assert 'OUT_CP_LM_ULM_DONE'
   *CtrlRegA = ctrl_mask;

   fflush(stdout);

   return;
   }


// ========================================================================================================
// ========================================================================================================
// ========================================================================================================

int main(int argc, char *argv[])
   {
   volatile unsigned int *CtrlRegA;
   volatile unsigned int *DataRegA;
   unsigned int ctrl_mask;

   char infile_name[MAX_STRING_LEN];
   char outfile_name[MAX_STRING_LEN];

   short *data_arr_in;
   short *histo_arr_out;
   short *software_histo;
   int num_vals;
   int load_unload;

   int precision_scaler = 16;

   struct timeval t0, t1;
   long elapsed; 

// ======================================================================================================================
// COMMAND LINE
   if ( argc != 2 )
      {
      printf("ERROR: LoadUnload.elf(): Datafile name (test_data_10vals.txt)\n");
      return(1);
      }

   sscanf(argv[1], "%s", infile_name);

// Open up the memory mapped device so we can access the GPIO registers.
   int fd = open("/dev/mem", O_RDWR|O_SYNC);

   if (fd < 0) 
      { printf("ERROR: /dev/mem could NOT be opened!\n"); exit(EXIT_FAILURE); }

// Add 2 for the DataReg (for an offset of 8 bytes for 32-bit integer variables)
   DataRegA = mmap(0, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_0_BASE_ADDR);
   CtrlRegA = DataRegA + 2;

// Allocate arrays
   if ( (data_arr_in = (short *)calloc(sizeof(short), MAX_DATA_VALS)) == NULL )
      { printf("ERROR: Failed to calloc data 'data_arr_in' array!\n"); exit(EXIT_FAILURE); }
   if ( (histo_arr_out = (short *)calloc(sizeof(short), MAX_HISTO_VALS)) == NULL )
      { printf("ERROR: Failed to calloc data 'histo_arr_out' array!\n"); exit(EXIT_FAILURE); }
   if ( (software_histo = (short *)calloc(sizeof(short), MAX_HISTO_VALS)) == NULL )
      { printf("ERROR: Failed to calloc data 'histo_arr_out' array!\n"); exit(EXIT_FAILURE); }

// Read the data from the input file
   num_vals = ReadData(MAX_STRING_LEN, MAX_DATA_VALS, infile_name, data_arr_in);

// Set the control mask to indicate enrollment. 
   ctrl_mask = 0;
      
// ==================================================================================
// Software computed values. Hardware reports mean WITH 4 bits of precision but range using ONLY the integer portion.
   gettimeofday(&t0, 0);
   ComputeHisto(MAX_DATA_VALS, num_vals, data_arr_in, (short)LV_BOUND, (short)HV_BOUND, (short)DIST_RANGE, precision_scaler,
      software_histo);
   gettimeofday(&t1, 0); elapsed = (t1.tv_sec-t0.tv_sec)*1000000 + t1.tv_usec-t0.tv_usec; 
   printf("\tSoftware Runtime %ld us\n\n", (long)elapsed);
// ==================================================================================

// Do a soft RESET
   *CtrlRegA = ctrl_mask | (1 << OUT_CP_RESET);
   *CtrlRegA = ctrl_mask;
   usleep(1000);

// Wait for the hardware to be ready -- should be on first check.
   while ( ((*DataRegA) & (1 << IN_SM_READY)) == 0 );

// Start clock
   gettimeofday(&t0, 0);

// Start the VHDL Controller
   *CtrlRegA = ctrl_mask | (1 << OUT_CP_START);
   *CtrlRegA = ctrl_mask;

// Controller expects data to be transferred to the BRAM as the first operation.
   load_unload = 0;
   LoadUnloadBRAM(MAX_STRING_LEN, MAX_DATA_VALS, num_vals, load_unload, data_arr_in, CtrlRegA, DataRegA, ctrl_mask);

// Data transfer in time
   gettimeofday(&t1, 0); elapsed = (t1.tv_sec-t0.tv_sec)*1000000 + t1.tv_usec-t0.tv_usec; 
   printf("\tHardware Transfer In time %ld us\n\n", (long)elapsed);

// Start clock
   gettimeofday(&t0, 0);

// Wait for 'stopped' to be asserted by hardware. When this occurs, histogram FSM is finished and its ready to transfer
// data out.
   while ( ((*DataRegA) & (1 << IN_SM_HANDSHAKE)) == 0 );

// Approx. runtime of hardware excluding I/O
   gettimeofday(&t1, 0); elapsed = (t1.tv_sec-t0.tv_sec)*1000000 + t1.tv_usec-t0.tv_usec; 
   printf("\tHardware Runtime %ld us\n\n", (long)elapsed);

// Check for a HISTO error 
   if ( ((*DataRegA) & (1 << IN_SM_HISTO_ERR)) == 1 )
      { printf("ERROR: Histogram error!\n"); exit(EXIT_FAILURE); }

// Start clock
   gettimeofday(&t0, 0);

// After computing the histogram, Controller expects to transfer histogram memory and distribution parameters back to C program
   load_unload = 1;
   LoadUnloadBRAM(MAX_STRING_LEN, MAX_HISTO_VALS, MAX_HISTO_VALS, load_unload, histo_arr_out, CtrlRegA, DataRegA, ctrl_mask);

// Data transfer out time
   gettimeofday(&t1, 0); elapsed = (t1.tv_sec-t0.tv_sec)*1000000 + t1.tv_usec-t0.tv_usec; 
   printf("\tHardware Transfer Out time %ld us\n\n", (long)elapsed);

// ==================================================================================
// Print out the histogram. The mean and range are the last two values (of the 2048).
   int i;
   printf("HISTOGRAM VALUES:\n");
   for ( i = 0; i < MAX_HISTO_VALS - 2; i++ )
      {
      printf("(%4d) %3d  ", i, histo_arr_out[i]);
      if ( ((i+1) % 10) == 0 )
         printf("\n");

// Sanity check. Both the hardware and software histogram should be identical.
      if ( histo_arr_out[i] != software_histo[i] )
         {
         printf("ERROR: Mismatch between hardware %d and software %d histos at index %d\n", 
            histo_arr_out[i], software_histo[i], i); exit(EXIT_FAILURE); 
         }
      }
   printf("\n\n");


// Write out an xy file with histogram data for histogram plotting with R. Assumes the input file is "yyy.txt"
   FILE *OUTFILE;
   char temp_str[MAX_STRING_LEN];

   strcpy(temp_str, infile_name);
   temp_str[strlen(infile_name) - 3]  = '\0';

   strcpy(outfile_name, "Output_");
   strcat(outfile_name, temp_str);
   strcat(outfile_name, "xy");
   
   if ( (OUTFILE = fopen(outfile_name, "w")) == NULL )
      { printf("ERROR: ReadData(): Could not open %s\n", outfile_name); exit(EXIT_FAILURE); }
   for ( i = 0; i < MAX_HISTO_VALS - 2; i++ )
      fprintf(OUTFILE, "%d\t%d\n", i, histo_arr_out[i]);
   fclose(OUTFILE);


   printf("Hardware Computed Mean %.4f\tRange %d\n", 
      (float)histo_arr_out[MAX_HISTO_VALS-2]/precision_scaler, histo_arr_out[MAX_HISTO_VALS-1]);
// ==================================================================================

// Check if Controller returned to idle
   if ( ((*DataRegA) & (1 << IN_SM_READY)) == 0 )
      { printf("ERROR: Controller did NOT return to idle!\n"); exit(EXIT_FAILURE); }

   return 0;
   } 
