#!   /usr/bin/env   python
#    coding: utf8
##_________________________________________________________________________________________________
##                                                                                                |
##                                           |TDC PTS|                                            |
##                                                                                                |
##                                         CERN,BE/CO-HT                                          |
##________________________________________________________________________________________________|

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                                     White Rabbit calibration                                   |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Calibration of the TDC Channel 1 with White Rabbit.                               |
##                                                                                                |
##              For the calibration, we plug a pre-calibrated 2 ns LEMO cable [1] to the TDC      |
##              Channel 1; the other end of the cable is connected to the PPS output of the White |
##              Rabbit master.                                                                    |
##              We also connect a fiber (of any lenght) between the SFP connector of the SPEC     | 
##              carrier and any of the 18 ports of the White Rabbit master (switch or node).      |
##                                                                                                |
##              With White Rabbit, through the exchange of Ethernet messages in the fiber, a      |
##              common notion of time is established between the TDC and the White Rabbit master. |
##              Therefore, the TAI pulse within the TDC core, that signals the arrival of a new   |
##              TAI second and the PPS pulse in the input of the TDC board, are in principle      |
##              expected to arrive as the following drawing shows:                                |
##                                                                                                |
##              TAI                  : ____________|-|_____________________________________       |
##                                                                                                |
##              PPS at the master    : ____________|-|_____________________________________       |
##                                                                                                |
##              PPS at TDC Channel 1 : _______________________________|-|__________________       |
##                                                  <---------------->                            |
##                                                          2 ns                                  |
##                                                                                                |
##              However, to this 2 ns we need to add the delays from the Channel 1 LEMO connector |
##              to the input of the ACAM chip (track lengths and input buffer delay).             |
##              This amount of delay differs slightly from board to board.                        |
##              There is also an offset added by the ACAM chip calculations, in the order of 293ns| 
##              which is fixed regardless of the board.                                           |
##                                                                                                |
##              The test is taking 512 timestamps of the PPs input and is averaging the results.  |
##                                                                                                |
## FW to load   .bin                                                                              |
## Authors      Evangelia Gousiou (Evangelia.Gousiou@cern.ch)                                     |
## Website      http://www.ohwr.org/projects/pts                                                  |
## Date         11/01/2013                                                                        |
##-------------------------------------------------------------------------------------------------

##-------------------------------------------------------------------------------------------------
##                               GNU LESSER GENERAL PUBLIC LICENSE                                |
##                              ------------------------------------                              |
## This source file is free software; you can redistribute it and/or modify it under the terms of |
## the GNU Lesser General Public License as published by the Free Software Foundation; either     |
## version 2.1 of the License, or (at your option) any later version.                             |
## This source 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 Lesser General Public License for more details.                                    |
## You should have received a copy of the GNU Lesser General Public License along with this       |
## source; if not, download it from http://www.gnu.org/licenses/lgpl-2.1.html                     |
##-------------------------------------------------------------------------------------------------

##-------------------------------------------------------------------------------------------------
##                                            Import                                             --
##-------------------------------------------------------------------------------------------------

# Import system modules
import sys
import time
import os
import math
from datetime import datetime

# Add common modules location to path
sys.path.append('../../../../pts/')
sys.path.append('../../../../pts/gnurabbit/python/')
sys.path.append('../../../../pts/common/')
sys.path.append('../../../../pts/common/fmceeprom/python')
sys.path.append('../../../../pts/common/usb_box')
sys.path.append('../../../../pts/common/cp210x')


# Import common modules
from ptsexcept import *
import rr
import csr
from PendulumCNT91 import *
from find_usb_tty import *

# Import specific modules
import fmc_tdc_sdb

from numpy import *
from ctypes import *
from fmc_eeprom import *

MEZZ_EEPROM_ADDR=0x50

PENDULUM           = "/dev/usbtmc1"
USB_DEVICE         = "/dev/ttyUSB0"
RS232_BAUD         = 57600

def pendulum_output_on(meas):
    meas.write("OUTP:TYPE PULS")
    meas.write("SOUR:PULS:PER 0.000005")
    meas.write("SOUR:PULS:WIDT 0.000001")
    return (1)

def pendulum_output_off(meas):
    meas.write("OUTP:TYPE OFF")
    return (1)

# Converts two's complement hex to signed
def hex2signed(value):
    if(value & 0x80000000):
        return -((~value & 0xFFFFFFFF) + 1)
    else:
        return value

##-------------------------------------------------------------------------------------------------
##                                             main                                              --
##-------------------------------------------------------------------------------------------------
def main (default_directory='.'):

    #------------> Ask if the user wants to make the White Rabbit calibration 
    # Standard in/out temporary redirection
    tmp_stdout = sys.stdout;
    sys.stdout = sys.__stdout__;
    tmp_stdin = sys.stdin;
    sys.stdin = sys.__stdin__;

    ask = raw_input("--> Please connect the DIO PPS to TDC Channel 1; press y/n when finished [y/n]: ")
    ask = ask.upper()
    if (ask == "N"):
        msg = ("Operator decided not to perform the White Rabbit calibration")
        # Standard in/out redirection
        sys.stdout = tmp_stdout;
        sys.stdin  = tmp_stdin;
        raise PtsWarning (msg)
    else:
        # Standard in/out redirection
        sys.stdout = tmp_stdout;
        sys.stdin  = tmp_stdin;

        # Constants declaration
        FMC_TDC_ADDR           = '1a39:0004/1a39:0004@000B:0000'
        FMC_TDC_BITSTREAM_PATH = '../firmwares/wrabbit_tdc.bin'
        FPGA_LOADER_PATH       = '../../../../pts/gnurabbit/user/fpga_loader'
        FAMILY_CODE            = 0x28
        WRABBIT_CLK_LOCK_MASK  = 0x10
        WRABBIT_CALIB_FILENAME = "wrabbit_calib_data.txt"
        WRABBIT_CALIB_FILENAME = os.path.join(default_directory, WRABBIT_CALIB_FILENAME)
        f_out = open(WRABBIT_CALIB_FILENAME, 'w')

        # SPEC object declaration
        spec = rr.Gennum()

        # Bind SPEC object to FMC TDC card
        print "\n-------------------------------------------------------------------"
        print "----------------------------- FMC TDC -----------------------------"
        print "-------------------- White Rabbit Calibration ---------------------\n"
        print ("White Rabbit calibration of the TDC Channel 1.\n")

        print "\n_______________________________Info______________________________\n"
        print "FMC TDC address to parse: %s"%(FMC_TDC_ADDR)
        for name, value in spec.parse_addr(FMC_TDC_ADDR).iteritems():
            print "%s:0x%04X"%(name, value)
        spec.bind(FMC_TDC_ADDR)

        print "\n_________________________Initialisations_________________________\n"
        # Load FMC TDC firmware
        print "Loading FMC TDC firmware...",
        firmware_loader = os.path.join(default_directory, FPGA_LOADER_PATH)
        bitstream = os.path.join(default_directory, FMC_TDC_BITSTREAM_PATH)
        os.system(firmware_loader + ' ' + bitstream)
        time.sleep(1)
        print "Firmware loaded!"

        # TDC object declaration
        print "Object declaration"
        tdc = fmc_tdc_sdb.CFMCTDC(spec)

        # --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * ---
        # Check bitsteam type
        bitstream_type = tdc.read_bitstream_type()
        if(bitstream_type == 0xFFFFFFFF):
            msg = ("FATAL ERROR: No access to TDC core")
            os.close(spec.fd)
            print (msg)
            raise PtsCritical (msg)
        if(bitstream_type != 0x10003):
            msg = ("FATAL ERROR: Wrong bitstream type loaded 0x%8X")%bitstream_type
            os.close(spec.fd)
            print (msg)
            raise PtsCritical (msg)
        else:
            print("Access to TDC core OK")


        # --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * ---
        # Access the TDC core for configuration
        print "\n_________________________TDC core config__________________________\n"

        # Configure ACAM and TDC core registers
        tdc.config_acam()
        time.sleep(1)
        tdc.reset_acam()
        acam_status_test = tdc.read_acam_status()-0xC4000800
        if acam_status_test == 0:
            print "ACAM IC8: Status register OK"
        else:
            msg = ("ERROR: ACAM IC8: No communication")
            print (msg)
            raise PtsError (msg)
        acam_readback_regs = []
        acam_readback_regs = tdc.readback_acam_config()
        for i in range(len(tdc.ACAM_READBACK_REGS)): 
            if (acam_readback_regs[i] == tdc.ACAM_READBACK_REGS[i]):
                print "ACAM IC8: reg 0x%02X: 0x%08X OK"%(tdc.ACAM_READBACK_ADDR[i], acam_readback_regs[i])
            else:
                msg = ("ERROR: ACAM IC8: Configuration registers failure; reg 0x%2X: received 0x%8X, expected 0x%8X"%(tdc.ACAM_READBACK_ADDR[i], acam_readback_regs[i],tdc.ACAM_READBACK_REGS[i]))
                print (msg)
                raise PtsError (msg)

        # Set thresholds for generation of interrupts
        #print ("\n")
        tdc.set_irq_tstamp_thresh(0x100)
        tdc.set_irq_time_thresh(0xFFFFFFFF)
        tdc.enable_tstamps_irq()
        tdc.clear_tstamps_irq()
        tdc.vic_ack_current_vector()

        # Enable channels
        tdc.enable_channels()
        #tdc.channel_term(1, 1)

        print "\n_________________________White Rabbit_____________________________\n"
        # White Rabbit stuff
        tdc.wrabbit_enable()
        print "White Rabbit enabled. Waiting for clock lock..",
        for i in range(25):
            wrabbit_status = tdc.wrabbit_status()
            if wrabbit_status & WRABBIT_CLK_LOCK_MASK != 0:
                print "locked!"
                print "White Rabbit status reg: 0x%.8X"%(wrabbit_status)
                tdc.get_local_utc()
                break
            else:
                time.sleep(1)
            if i == 24:
                msg = "ERROR: White Rabbit: Unable to lock"
                print (msg)
                raise PtsError (msg)

        # Enable timestamps aquisition (TDC_START_FPGA pulse sent at this step) 
        print "\n_______________________Start of Aquisition________________________\n"
        acam_start01 = tdc.start_acq()
        all_measurs_min = []
        all_measurs_max = []
        all_measurs_avg = []
        lengths = []

        # --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * --- * ---
        for i in range(2):

            tdc.check_irq(0)
            irq_src = tdc.get_eic_src()
            irq_vect = tdc.vic_get_current_vector_addr()
            if(irq_vect == tdc.fmc_eic_addr):
                if(tdc.get_tstamps_status()):
                    tdc.clear_tstamps_irq()
                    tdc.vic_ack_current_vector()

            # Timestamps retreival
            print ("\n-------> Iteration %d")%(i+1)
            tdc_write_pointer_now = tdc.get_pointer()
            tdc_overflows_now     = tdc.get_overflow_counter()
            print "TDC Write pointer        : %d"%(tdc_write_pointer_now)
            print "TDC Overflows            : %d"%(tdc_overflows_now)
            if tdc_overflows_now != (i+1):
                msg = ("ERROR: Unexpected error with interrupts; expected overflows: %d; received overflows: %d")%((i+1),tdc_overflows_now)
                print (msg)
                raise PtsError (msg)
            timestamps, data = tdc.get_timestamps_nodma_noutc(0)

           # keep only rising edge timestamps
            r_edge_timestamps = []
            for m in range(len(timestamps)):
                if (timestamps[m][2] == 1 and (timestamps[m][1] == 0)): # rising tstamps ch1
                    r_edge_timestamps.append(timestamps[m][0])

            if len(r_edge_timestamps) == 128:
                r_edge_timestamps.sort()
            else:
                msg = ("ERROR: Ch1 registered timestamps: %d; expected: %d \n")%(len(r_edge_timestamps), 128)
                print (msg)
                raise PtsError (msg)

            current_max = max(r_edge_timestamps)
            current_min = min(r_edge_timestamps)
            span = current_max-current_min
            avg = (sum(r_edge_timestamps, 0.0) / len(r_edge_timestamps))

            print "rising tstamps           : %d"%len(r_edge_timestamps)
            print "current span             : %.2f ps"%span
            print "current max              : %.2f ps"%current_max
            print "current min              : %.2f ps"%current_min
            print "current avg              : %.2f ps"%avg

            all_measurs_max.append(current_max)
            all_measurs_min.append(current_min)
            all_measurs_avg.append(avg)

        span = (max(all_measurs_max)-min(all_measurs_min))
        if span > 1400:
            msg = "ERROR: White Rabbit calibration: Measurements span %.2f is out of the accepted limits [-700ps..700ps]."%span
            print (msg)
            raise PtsError (msg)
        else:
            print ("\n-------> Results from all Iterations")
            print "span                     : %.2f ps"%span
            print "max                      : %.2f ps"%(max(all_measurs_max))
            print "min                      : %.2f ps"%(min(all_measurs_min))
            print "avg                      : %.2f ps"%(sum(all_measurs_avg, 0.0) / len(all_measurs_avg))
            wr_calib_value = (((sum(all_measurs_avg, 0.0) / len(all_measurs_avg)) - 1200)*100) # finally the 2 ns cable was measured 1.2
            print "\n\nCalibration value        : %.2f ps"%(wr_calib_value/100)    
            print "Calibration hex value written in file: %s"%WRABBIT_CALIB_FILENAME
            print"[ ",
            print ("0x%.8X ")%(wr_calib_value),
            print"]"
            f_out.write('0x%.8X\n'%(wr_calib_value))
        f_out.close()
        print "\n\n--------------------------------"
        end_test_time = time.time()

        print "\n\n-----------------------------------------------------------------"

if __name__ == '__main__' :
    main()
