#!   /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 Pendulum as pulse generator, sending|
##              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   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 numpy import *
from ctypes import *
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/usb_box')
sys.path.append('../../../../pts/common/cp210x')

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

# Pendulum
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.008")
    meas.write("SOUR:PULS:WIDT 0.0002")
    return (1)

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

# USB-interconnection-box
BOX_USB_VENDOR_ID = 0x10c4 # Cygnal Integrated Products, Inc.
BOX_USB_DEVICE_ID = 0xea60 # CP210x Composite Device

NUM_OF_TSTAMPS     = 10    # number of fill-ups of the TDC memory
                           # ex NUM_OF_TSTAMPS = 10, means aquisition
                           # of 2560 timestamps; in this setup this means
                           # 640 rising edge timestamps per channel

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

def main (default_directory='.'):

    start_test_time = time.time()

#-----> PCIe address, firmware, loader constants
    FMC_TDC_ADDR             = '1a39:0004/1a39:0004@000B:0000'
    FMC_TDC_BITSTREAM_PATH   = '../firmwares/tdc_allothertests.bit'
    FPGA_LOADER_PATH         = '../../../../pts/gnurabbit/user/fpga_loader'

#-----> FPGA initializations
    # SPEC object declaration
    spec = rr.Gennum()

    # 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 Pendulum as pulse\n"
           "generator, sending 1 Hz pulses to TDC channel 1. The test configures\n"
           "the ACAM and then the DAC through its SPI interface. Two values are\n"
           "set in the DAC: first, a low one (0V) and then a higher one (1.6V).\n"
           "The ACAM timestamps are retrieved and evaluated: the pulse duration\n"
           "is expected to be higher when the DAC value is high and lower when\n"
           "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)
    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")
        print (msg)
        raise PtsError (msg)
    if(bitstream_type != 0x1):
        msg = ("FATAL ERROR: Wrong bitstream type loaded 0x%08X") %bitstream_type
        print (msg)
        raise PtsError (msg)
    else:
        print('Access to TDC core OK')


#-----> ACAM and TDC configuration
    # 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")
        print (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)

    # DAC
    tdc.configure_mezz_dac(0xA9CF)

    # Interrupts
    print "\n____________________________IRQs_________________________________\n"
    print('Set IRQ enable mask: %.4X')%tdc.set_irq_en_mask(0x1F)
    tdc.set_irq_tstamp_thresh(0x100)
    tdc.set_irq_time_thresh(0xFFFFF)

    # Enable channels and terminations of all channels
    tdc.enable_channels()
    tdc.channel_term(1, 1)
    tdc.channel_term(2, 1)
    tdc.channel_term(3, 1)
    tdc.channel_term(4, 1)
    tdc.channel_term(5, 1)


#-----> USB-interconnection-box
    # Communication and Setup of Interconnection box
    print "\n_____________________USB interconnection box______________________\n"
    box_tty = CttyUSB().find_usb_tty(BOX_USB_VENDOR_ID, BOX_USB_DEVICE_ID)
    print("Device: %s" % box_tty[0])
    box = CCalibr_box(box_tty[0])
    box.select_trigger_loopback(0)
    box.select_output(4)


#-----> Pendulum
    # Communication with Pendulum
    print "\n__________________________USB Pendulum_____________________________\n"
    try:
        meas = PendulumCNT91(PENDULUM)
    except OSError:
        raise PtsError('CNT-91 is not switched on or badly connected, aborting.')
    a = pendulum_output_off(meas)
    # dummy sequence, coz it has been noted that otherwise few pulses of the
    # configuration of test10 are being sent before switching to the new configuration
    a = pendulum_output_on(meas)
    time.sleep(0.2)
    a = pendulum_output_off(meas)


#-----> TDC
    # Enable channels and start timestamps acquisition (TDC_START_FPGA pulse sent at this step)
    print "\n______________________ACAM acquisition start________________________\n"
    tdc.start_acq()
    print ("Sending pulses of period 8 ms to the TDC_STOP1 channel")


#-----> ##############    DAC: 0x0000    ##############
    print "\n____________________DAC 0x0000: 0V test______________________\n"
    tdc.configure_mezz_dac(0x0)

    r_edge_timestamps_ch1 = []
    all_measurs           = []

    # pendulum output on
    a = pendulum_output_on(meas)

    # TDC aquisition of NUM_OF_TSTAMPS*64 timestamps per channel
    for j in range(NUM_OF_TSTAMPS):
        tdc.check_irq(0)
        timestamps, data = tdc.get_timestamps(0)
        # keep only rising edge timestamps
        for m in range(len(timestamps)):
            if (timestamps[m][2] == 1)   and (timestamps[m][1] == 0):       # rising tstamps ch1
                r_edge_timestamps_ch1.append(timestamps[m][0])
        # clear interrupt
        tdc.clear_irq_src(int(math.log(tdc.get_irq_src(),2)))

    # pendulum output off
    a = pendulum_output_off(meas)

    # keep only rising edge timestamps
    tdc_write_pointer_now = tdc.get_pointer()
    tdc_overflows_now     = tdc.get_overflow_counter()
    if tdc_overflows_now != NUM_OF_TSTAMPS:
        msg = ("ERROR: Unexpected error with interrupts; expected overflows: %d; received overflows: %d")%((NUM_OF_TSTAMPS*(i+1)),tdc_overflows_now)
        print (msg)
        raise PtsError (msg)

    print "Ch1 tstamps      : %d"%(len(r_edge_timestamps_ch1))
    print "TDC write pointer: %d"%(tdc_write_pointer_now)
    print "TDC overflows    : %d"%(tdc_overflows_now)
    measurs_ch1     = []
    # check number of pulses arrived
    if (len(r_edge_timestamps_ch1) > 63*NUM_OF_TSTAMPS) or (len(r_edge_timestamps_ch1) < 60*NUM_OF_TSTAMPS):
        r_edge_timestamps_ch1.sort()

    # analyse the timestamps for Ch1
        for k in range(0, len(r_edge_timestamps_ch1)-1, 1):
            measur = r_edge_timestamps_ch1[k+1] - r_edge_timestamps_ch1[k]
            if (measur < 8999995000) or (measur < 9000005000):
                measurs_ch1.append (measur)
        print "samples          : %d"%(len(measurs_ch1))
        # evaluate the timestamps for Ch1
        if (len(measurs_ch1) < 600) or (len(measurs_ch1) > 650):
            msg = ("ERROR: Ch1 received %d timestamps of 8ms period; expected: around %d \n")%(len(measurs_ch1), 64*NUM_OF_TSTAMPS)
            print (msg)
            raise PtsError (msg)
        else:
            avg_0x0000 = ((sum(measurs_ch1, 0.0) / len(measurs_ch1)))
            print "span             : %.2f ps"%((max(measurs_ch1))-(min(measurs_ch1)))
            print "average          : %.2f ms"%(avg_0x0000)
            print "max iter %d %.2f ps"%(measurs_ch1.index(max(measurs_ch1)), (max(measurs_ch1)))
            print "min iter %d %.2f ps"%(measurs_ch1.index(min(measurs_ch1)), (min(measurs_ch1)))
    else:
        msg = ("ERROR: Ch1 registered timestamps: %d; expected: around %d \n")%(len(r_edge_timestamps_ch1), 64*NUM_OF_TSTAMPS)
        print (msg)
        raise PtsError (msg)


#-----> ##############    DAC: 0xAA57    ##############
    print "\n____________________DAC 0xAA57: 0V test______________________\n"
    tdc.configure_mezz_dac(0xAA57)

    r_edge_timestamps_ch1     = []
    all_measurs               = []

    # pendulum output on
    a = pendulum_output_on(meas)

    # TDC acquisition of NUM_OF_TSTAMPS*64 timestamps per channel
    for j in range(NUM_OF_TSTAMPS):
        tdc.check_irq(0)
        timestamps, data = tdc.get_timestamps(0)
        # keep only rising edge timestamps
        for m in range(len(timestamps)):
            if (timestamps[m][2] == 1)   and (timestamps[m][1] == 0):       # rising tstamps ch1
                r_edge_timestamps_ch1.append(timestamps[m][0])
        # clear interrupt
        tdc.clear_irq_src(int(math.log(tdc.get_irq_src(),2)))

    # pendulum output off
    a = pendulum_output_off(meas)

    # data manipulation

    # keep only rising edge timestamps
    tdc_write_pointer_now = tdc.get_pointer()
    tdc_overflows_now     = tdc.get_overflow_counter()
    if tdc_overflows_now != NUM_OF_TSTAMPS*2:
        msg = ("ERROR: Unexpected error with interrupts; expected overflows: %d; received overflows: %d")%((NUM_OF_TSTAMPS*(i+1)),tdc_overflows_now)
        print (msg)
        raise PtsError (msg)

    print "Ch1 tstamps      : %d"%(len(r_edge_timestamps_ch1))
    print "TDC write pointer: %d"%(tdc_write_pointer_now)
    print "TDC overflows    : %d"%(tdc_overflows_now)
    measurs_ch1     = []
    # check number of pulses arrived
    if (len(r_edge_timestamps_ch1) > 63*NUM_OF_TSTAMPS) or (len(r_edge_timestamps_ch1) < 60*NUM_OF_TSTAMPS):
        r_edge_timestamps_ch1.sort()

    # analyse the timestamps for Ch1
        for k in range(0, len(r_edge_timestamps_ch1)-1, 1):
            measur = r_edge_timestamps_ch1[k+1] - r_edge_timestamps_ch1[k]
            if (measur < 8999995000) or (measur < 9000005000):
                measurs_ch1.append (measur)
        print "samples          : %d"%(len(measurs_ch1))
        # evaluate the timestamps for Ch1
        if (len(measurs_ch1) < 600) or (len(measurs_ch1) > 650):
            msg = ("ERROR: Ch1 received %d timestamps of 8ms period; expected: around %d \n")%(len(measurs_ch1), 64*NUM_OF_TSTAMPS)
            print (msg)
            raise PtsError (msg)
        else:
            print "span             : %.2f ps"%((max(measurs_ch1))-(min(measurs_ch1)))
            avg_0xaa57 = ((sum(measurs_ch1, 0.0) / len(measurs_ch1)))
            print "average          : %.2f ms"%(avg_0xaa57)
            print "max iter %d %.2f ps"%(measurs_ch1.index(max(measurs_ch1)), (max(measurs_ch1)))
            print "min iter %d %.2f ps"%(measurs_ch1.index(min(measurs_ch1)), (min(measurs_ch1)))
    else:
        msg = ("ERROR: Ch1 registered timestamps: %d; expected: around %d \n")%(len(r_edge_timestamps_ch1), 64*NUM_OF_TSTAMPS)
        print (msg)
        raise PtsError (msg)


    print "\n_______________________DAC Test Result_______________________\n"
    if (avg_0xaa57 > avg_0x0000 + 100000):
        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)


    print "\n\n--------------------------------"
    end_test_time = time.time()
    print "Test elapsed time: %.2f seconds\n\n\n" % (end_test_time-start_test_time)


if __name__ == '__main__' :
    main()
