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

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                                          TDC DAC test                                          |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Testing of the DAC IC2. The test is using the Fine Delay board as pulse generator,|
##              sending 1 Hz pulses to TDC channel 1. The firmware loaded on the carrier FPGA     |
##              configures the ACAM; it also configures the DAC through its SPI interface.        |
##              Two values are set in the DAC: first, a low one (0V) and then a higher one (1.6V).|
##              The ACAM timestamps are retrieved and evaluated: the pulse duration is expected   |
##              to be higher when the DAC value is high and lower when the DAC value is low.      |
##                                                                                                |
## 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 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 "---------------------------- DAC test -----------------------------\n"
    print ("Testing of the DAC IC2. The test is using the Fine Delay board as pulse\n"
           "generator, sending 1 Hz pulses to TDC channel 1. The test configures the\n"
           "ACAM and then the DAC through its SPI interface. Two values are set in the\n"
           "DAC: first, a low one (0V) and then a higher one (1.6V). The ACAM timestamps\n"
           "are retrieved and evaluated: the pulse duration is expected to be higher\n"
           "when the DAC value is high and lower when the DAC value is low.\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 termination of channel 1
    tdc.enable_channels()
    tdc.channel_term(1, 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...",
    sys.stdout.flush()
    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(1)
    print "Firmware loaded!"

    # 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
    channel        = 1
    enable         = 1
    t_start_coarse = 0
    width          = 100000       # pulse width 100 ns
    delta          = 100000000000 # rising edges every 1 sec
    count          = 6

    one_sec_measur_list_0x0000 = []
    one_sec_measur_list_0xAA57 = []

    for m in range(2):  # 2 iterations; one with DAC at 0x0000, one with DAC 0xAA57

        time.sleep(0.5) # somehow needed

#-----> Fine Delay
        # Bind SPEC object to FMC Fine Delay card
        spec.bind(FMC_DELAY_ADDR)

        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)

        # Configuring DAC
        if m == 0:
            print "\n____________________DAC 0x0000: 0V test______________________\n"
            tdc.configure_mezz_dac(0x0)
        else:
            print "\n____________________DAC 0xAA57: 1.6V test____________________\n"
            tdc.configure_mezz_dac(0xAA57)

        time.sleep(7)

        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 1s period to TDC channel 1"%(count)
        print "TDC Write pointer                 : %d"%(tdc.get_pointer())

        if (tdc.get_pointer()) == 0:
            msg = ("ERROR: TDC_IN_FPGA1: No pulse arrived")
            os.close(spec.fd)
            print (msg)
            raise PtsError (msg)
        else:
            timestamps, data = tdc.get_timestamps(0)

        # keep rising edge timestamps
        r_edge_timestamps = []
        for i in range(len(timestamps)):
             if ((timestamps[i][2] == 1)):
                 r_edge_timestamps.append(timestamps[i][0])
        r_edge_lgth = len(r_edge_timestamps)
        print "Number of rising edge timestamps  : %d"%(r_edge_lgth)

        if m == 0:
            if (r_edge_lgth) != 6:
                msg = (("ERROR: TDC_IN_FPGA1: %d pulses arrived; 6 were expected") %(r_edge_lgth))
                os.close(spec.fd)
                print (msg)
                raise PtsError (msg)

            for k in range(0,len(r_edge_timestamps),2):
                one_sec_measur_0x0000 = r_edge_timestamps[k+1] - r_edge_timestamps[k]
                one_sec_measur_list_0x0000.append(one_sec_measur_0x0000)

            avg_dac_0x0000 = (sum(one_sec_measur_list_0x0000, 0.0) / len(one_sec_measur_list_0x0000))
            print "\nDAC 0x0000: One second measurement: %.2f ps"%avg_dac_0x0000

        else:
            if (r_edge_lgth) != 12:
                msg = (("ERROR: TDC_IN_FPGA1: %d pulses arrived; 6 were expected") %(r_edge_lgth))
                os.close(spec.fd)
                print (msg)
                raise PtsError (msg)

            for k in range(6,len(r_edge_timestamps),2):
                one_sec_measur_0xAA57 = r_edge_timestamps[k+1] - r_edge_timestamps[k]
                one_sec_measur_list_0xAA57.append(one_sec_measur_0xAA57)

            avg_dac_0xAA57 = (sum(one_sec_measur_list_0xAA57, 0.0) / len(one_sec_measur_list_0xAA57))
            print "\nDAC 0xAA57: One second measurement: %.2f ps"%avg_dac_0xAA57



    print "\n_______________________DAC Test Result_______________________\n"
    if (avg_dac_0xAA57 > avg_dac_0x0000 + 1000000):
        print("DAC test OK!")
    else:
        msg = ("ERROR: DAC IC14: Changes in DAC are not affecting the clocks")
        os.close(spec.fd)
        print (msg)
        raise PtsError (msg)

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


if __name__ == '__main__' :
    main()
