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

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                                       TDC Channels test                                        |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Testing of the ACAM IC8 timestamps, TDC_START_FPGA signal, TDC_STOP signals,      |
##              TDC_INT signal, TDC_EMPTY_FLAG signals.                                           |
##              The Fine Delay is used as pulse generator sending pulses to all the TDC channels. |
##              The test is retrieving the pulses and is checking their accuracy.                 |
##                                                                                                |
##                                                                                                |
## FW to load   tdc_allothertests.bit                                                             |
## 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 tp path
sys.path.append('../../../')
sys.path.append('../../../gnurabbit/python/')
sys.path.append('../../../common/')

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

# Import specific modules
import fmc_tdc
sys.path.append('../../../common/fmc_delay/software/python/')
import fdelay_lib


##-------------------------------------------------------------------------------------------------
##                                             Main                                              --
##-------------------------------------------------------------------------------------------------

def main (default_directory='.'):

    # Constants declaration
    FMC_TDC_ADDR             = '1a39:0004/1a39:0004@000B:0000'
    FMC_TDC_BITSTREAM_PATH   = '../firmwares/tdc_allothertests.bit'
    FMC_DELAY_ADDR           = '1a39:0004/1a39:0004@0005:0000'
    FMC_DELAY_BITSTREAM_PATH = '../firmwares/fmc_delay_spec.bin'
    FPGA_LOADER_PATH         = '../../../gnurabbit/user/fpga_loader'
    FMC_TDC_CHANNEL_NB       = 5

    # SPEC object declaration
    spec = rr.Gennum()


#-----> TDC
    # Bind SPEC object to FMC TDC card
    print "\n-------------------------------------------------------------------"
    print "----------------------------- FMC TDC -----------------------------"
    print "-------------------- TDC_STOP channels test -----------------------\n"
    print ("Test of the accuracy of the ACAM timestamps for each one of the channels.\n"
           "The test is using the Fine Delay board as pulse generator, sending\n"
           "several pulses to each one of the TDC channels. The test configures\n"
           "the ACAM chip and then retrieves and evaluates the generated timestamps.\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)

    # Load FMC TDC firmware
    print "\n_________________________Initialisations_________________________\n"
    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)
    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
    tdc = fmc_tdc.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 != 0x1):
        msg = ("FATAL ERROR: Wrong bitstream type loaded")
        os.close(spec.fd)
        print (msg)
        raise PtsCritical (msg)
    else:
        print('Access to TDC core OK')

    # Configure ACAM and TDC core registers
    print "\n__________________________Configuration__________________________\n"
    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")
        os.close(spec.fd)
        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]))
            os.close(spec.fd)
            print (msg)
            raise PtsError (msg)


    # Enable terminations of channels 1,2,3,4
    # channels 4 and 5 are receiving the same Fine Delay pulse; only channel 5 termination activated
    tdc.enable_channels()
    tdc.channel_term(1, 1)
    tdc.channel_term(2, 1)
    tdc.channel_term(3, 1)
    tdc.channel_term(4, 1)


    # Enable timestamps aquisition (TDC_START_FPGA pulse sent at this step) 
    tdc.start_acq()


#-----> Fine Delay
    # Bind SPEC object to FMC Fine Delay card
    print "\n___________________________Fine Delay____________________________"
    print "\nFine Delay initializations.."
    print "Fine Delay address to parse %s"%(FMC_DELAY_ADDR)
    for name, value in spec.parse_addr(FMC_DELAY_ADDR).iteritems():
        print "%s:0x%04X"%(name, value)
    spec.bind(FMC_DELAY_ADDR)

    # Load FMC Fine Delay firmware
    print "\nLoading FMC Fine Delay firmware...",
    firmware_loader = os.path.join(default_directory, FPGA_LOADER_PATH)
    bitstream = os.path.join(default_directory, FMC_DELAY_BITSTREAM_PATH)
    os.system(firmware_loader + ' ' + bitstream)
    time.sleep(2)
    print "Firmware loaded!"

    time.sleep(2)

    # Fine Delay object declaration
    try:
        fdelay = fdelay_lib.FineDelay(spec.get_fd())
    except:
        msg = ("ERROR: Fine Delay board Initiliazation failed")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

    # Set UTC and Coarse time in the Fine Delay
    fdelay.set_time(0, 0)
    fd_time = fdelay_lib.fd_timestamp()
    fd_time = fdelay.get_time()

    # Configure the Fine Delay as a pulse generator
    enable         = 1
    t_start_coarse = 0
    width          = 100000 # pulse width 100 ns
    delta          = 200000 # rising edges every 200 ns
    count          = 6


    # string to keep the ERROR messages from all the channels
    all_msg = ""

    for i in range(4):  # one iteration per channel;
                        # channels 4 and 5 receive pulses at the same time

        time.sleep(0.5) # needed between iterations


        r_edge_timestamps     = []
        r_edge_timestamps_ch5 = []
        all_measurs           = []
        all_measurs_ch5       = []

#-----> Fine Delay
        # Bind SPEC object to FMC Fine Delay card
        spec.bind(FMC_DELAY_ADDR)
        channel = i+1
        t_start_utc = fdelay.get_time().utc+1 # starting in  1 sec
        fdelay.conf_pulsegen(channel, enable, t_start_utc, t_start_coarse, width, delta, count)

#-----> TDC
        # Bind SPEC object to FMC TDC card
        spec.bind(FMC_TDC_ADDR)

        # Check if timestamps arrived
        print "\n_________________________TDC_STOP%d test____________________________\n" %(i+1)
        time.sleep(1.5)
        print "The time now is                   : %s" %datetime.now()
        print "The temperature now is            : %3.3f°C" %tdc.mezz_get_temp()
        print "Fine Delay sent                   : %d pulses of 200ns period"%(count)
        print "TDC Write pointer                 : %d"%(tdc.get_pointer())

        if (tdc.get_pointer()) != 0:
            timestamps, data = tdc.get_timestamps(0)
            # keep only rising edge timestamps belonging to channel under test
            for m in range(len(timestamps)):
                if ((timestamps[m][2] == 1) and (timestamps[m][1] == channel-1)):
                    r_edge_timestamps.append(timestamps[m][0])
                if ((timestamps[m][2] == 1) and (timestamps[m][1] == 4)):
                    r_edge_timestamps_ch5.append(timestamps[m][0])

        # check number of pulses arrived
        if ((len(r_edge_timestamps)) == 0):
            msg = (("ERROR: TDC_STOP%d: No pulse arrived ") %channel)
            print (msg)
            all_msg+=msg
            if ((len(r_edge_timestamps_ch5) == 0) and i == 3):
                msg = ("ERROR: TDC_STOP5: No pulse arrived ")
                print (msg)
                all_msg+=msg
        elif (len(r_edge_timestamps)) == 6:
            print "Number of rising edge timestamps  : %d"%(len(r_edge_timestamps))
        else:
            msg = (("ERROR: TDC_STOP%d: Unexpected number of pulses arrived; expected 6, arrived %d ")%(channel, (len(r_edge_timestamps))))
            print (msg)
            all_msg+=msg

        # if corrent number of timestamps has arrived, continue with the math
        if (len(r_edge_timestamps)) == 6:

            # evaluate timestamps: for each pulse, calculate the delta; should be 200 ns
            for k in range(0,len(r_edge_timestamps),2):
               measur = r_edge_timestamps[k+1] - r_edge_timestamps[k]
               all_measurs.append(measur)

            # average the delta from the 6 pulses
            avg = (sum(all_measurs, 0.0) / len(all_measurs))
            if (avg > 198800) and (avg < 201200): # +/-1200 ps acceptance limits (+/-500 ps from FD plus +/-700ps from TDC)
                print "Pulse delta measurement           : %.2f ps"%(avg)
            else: 
                msg = (("ERROR: ACAM IC8: Wrong timestamps; pulse delta %.2f ps is outside accepted limits [198800 ps..201200 ps] ") %avg[i])
                print (msg)
                all_msg+=msg

            if i == 3: # for channel 5 that receives pulses along with channel 4 

                print "\n________________________TDC_STOP5 test__________________________\n"

                # check number of pulses arrived
                if (len(r_edge_timestamps_ch5)) == 0:
                    msg = ("ERROR: TDC_STOP5: No pulse arrived ")
                    print (msg)
                    all_msg+=msg
                elif (len(r_edge_timestamps_ch5)) == 6:
                    print "Number of rising edge timestamps  : %d"%(len(r_edge_timestamps_ch5))
                else:
                    msg = (("ERROR: TDC_STOP5: Unexpected number of pulses arrived; expected 6, arrived %d ")%((len(r_edge_timestamps_ch5))))
                    print (msg)
                    all_msg+=msg

                if ((len(r_edge_timestamps_ch5)) == 6):
                    # channel 5: for each pulse, calculate the delta; should be 200 ns
                    for k in range(0,len(r_edge_timestamps_ch5),2):
                        measur = r_edge_timestamps_ch5[k+1] - r_edge_timestamps_ch5[k]
                        all_measurs_ch5.append(measur)

                    # channel 5: average the delta from the 6 pulses
                    avg = ((sum(all_measurs_ch5, 0.0) / len(all_measurs_ch5)))
                    if (avg > 198800) and (avg < 201200):
                        print "Pulse delta measurement           : %.2f ps"%(avg)
                    else:
                        msg = (("ERROR: ACAM IC8: Wrong timestamps; pulse delta %.2f ps is outside accepted limits [198800 ps..201200 ps] ") %avg)
                        print (msg) 
                        all_msg+=msg


        del r_edge_timestamps
        del all_measurs

    if (all_msg != ""):
        os.close(spec.fd)
        print (all_msg)
        raise PtsError (all_msg)


    os.close(spec.fd)
    print "\n-----------------------------------------------------------------\n\n\n"


if __name__ == '__main__' :
    main()
