/*
 A command-line FX2/FPGA memory testing tool. This program writes a random
 block of data to the memory, reads it out and verifies. Can be programmed
 to cycle multiple times. Inidividual and aggregate transfer rates are
 measured as well.
 
 Copyright (c) 2006 Dimtel, Inc., All Rights Reserved

 $Id: memtest.c,v 1.2 2013/10/08 19:27:17 dim Exp $
 $Date: 2013/10/08 19:27:17 $
 $Author: dim $
 $Revision: 1.2 $
 $Log: memtest.c,v $
 Revision 1.2  2013/10/08 19:27:17  dim
 Added BOARD_SERNO support to all command-line utilities

 Revision 1.1  2012/11/11 05:25:19  dim
 Moved from iGp12/src/usbio, updated to handle LLRF4 and iGp12

 Revision 1.1.1.1  2010/10/05 16:42:44  dim
 Initial import of iGp12

 Revision 1.2  2010/03/12 19:04:59  dim
 Updates for multiple boards, bug fixes

 Revision 1.1.1.1  2009/06/02 20:26:02  dim
 Initial import

 Revision 1.2  2009/06/02 19:56:51  dim
 Full support for service functions (fw load, 8051 control, EEPROM) added

 Revision 1.1.1.1  2006/11/16 19:38:15  dim
 Initial import

*/

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "usbio.h"

extern char *exnm;

/* Rescale time and rate appropriately */
void scales(double *et, double *rate, char **ru, char **tu)
{
  int scale = 0;
  char *r_units = NULL, *t_units = NULL;

  while (*rate > 1024.0)
  {
    scale++;
    *rate /= 1024.0;
  }
  switch (scale)
  {
    case 0:
      r_units = "B/s";
      break;
    case 1:
      r_units = "KiB/s";
      break;
    case 2:
      r_units = "MiB/s";
      break;
    case 4:
      r_units = "GiB/s";
      break;
  }

  scale = 0;
  while (*et < 1)
  {
    scale++;
    *et *= 1000.0;
  }
  switch (scale)
  {
    case 0:
      t_units = "s";
      break;
    case 1:
      t_units = "ms";
      break;
    case 2:
      t_units = "us";
      break;
    case 4:
      t_units = "ns";
      break;
  }
  
  *ru = r_units;
  *tu = t_units;
}

int main(int argc, char **argv)
{
  char *r_units, *t_units;
  unsigned int i, k, addr, len, count, rc, *data, *rdbk, width, mask;
  double delta_int = 0.0, d_total, rate;
  struct timeval tv[2], tv_total[2];
  usbio_context *ctxt;

  exnm = basename(argv[0]);

  if (argc < 4)
  {
    fprintf(stderr, "Usage: %s address length count [width]\n", exnm);
    return 0;
  }
  
  addr = (unsigned int) strtoul(argv[1], NULL, 0);
  len  = (unsigned int) strtoul(argv[2], NULL, 0);
  count = (unsigned int) strtoul(argv[3], NULL, 0);
  
  if (argc == 5)
    width = (unsigned int) strtoul(argv[4], NULL, 0);
  else
    width = 32;
    
  mask = (unsigned int)((1ull << (unsigned long long)width) - 1);

  ctxt = usbio_open_serial(0, getenv("BOARD_SERNO"));
  if (ctxt == NULL)
  {
    fprintf(stderr, "%s: usbio_open() failed\n", exnm);
    return 1;
  }
  
  data = (unsigned int *)malloc(len * sizeof(unsigned int));
  rdbk = (unsigned int *)malloc(len * sizeof(unsigned int));
  
  gettimeofday(tv_total, NULL);
  for (i = 0; i < count; i++)
  {
    for (k =0; k < len; k++)
      data[k] = rand();
    gettimeofday(tv, NULL);
    rc = usbio_block_write(ctxt, addr, data, len);
    if (rc != len)
    {
      fprintf(stderr, "%s: usbio_block_write() failed\n", exnm);
      return 2;
    }
#if 1
    rc = usbio_block_read(ctxt, addr, rdbk, len);
    gettimeofday(tv + 1, NULL);
#ifdef PRINT_ITER
    printf("Iteration %4d, time %.1f ms, %.3f MiB/s\n", i,
         (double)deltaT(tv, tv + 1)/1.0e3,
         ((double)len*2/(double)deltaT(tv, tv+1))*sizeof(int)/1024.0e-6/1024);
#endif
    delta_int += (double)deltaT(tv, tv+1);
    if (rc != len)
    {
      fprintf(stderr, "%s: usbio_block_read() failed (%d/%d)\n", exnm, rc, len);
      return 3;
    }
    for (k = 0; k < len; k++)
      if ((data[k] ^ rdbk[k]) & mask)
      {
        fprintf(stderr,
                "%s: Readback error at 0x%08x: wrote 0x%08x, read 0x%08x\n",
                exnm, addr+k, data[k], rdbk[k]);
        return 4;
      }
#endif
  }
  
  gettimeofday(tv_total + 1, NULL);
  
  /* Compute time difference - total */
  d_total = (double)deltaT(tv_total, tv_total + 1)/1.0e6;
  rate = ((double)len*2/d_total)*sizeof(int)*count; /* bytes/s */
  scales(&d_total, &rate, &r_units, &t_units);
  
  printf("Total time\t%.1f %s, %.3f %s\n", d_total, t_units, rate, r_units);

  delta_int /= 1.0e6;
  rate = ((double)len*2/delta_int)*sizeof(int)*count;
  scales(&delta_int, &rate, &r_units, &t_units);
  printf("Integrated time\t%.1f %s, %.3f %s\n", 
         delta_int, t_units, rate, r_units);
  usbio_close(ctxt, 0);
  
  return 0;
}

