#!   /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 test is using the Pendulum as pulse generator that in collaboration with the  |
##              Timetech Fanout and the USB-interconnection-box is sending pulses to each one of  |
##              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 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 "---------------------- ACAM accuracy test -------------------------\n"
    print ("Test of the accuracy of the ACAM timestamps for each one of the\n"
           "channels. The test is using the Pendulum as pulse generator that\n"
           "in collaboration with the Timetech Fanout and the\n"
           "USB-interconnection-box is sending pulses to each one of the TDC\n"
           "channels. The test configures the ACAM chip and then retrieves\n"
           "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)
    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)

#-----> 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)


#-----> TDC
    # Enable channels and start timestamps acquisition (TDC_START_FPGA pulse sent at this step)
    print "\n______________________ACAM acquisition start________________________\n"

    # 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)

    tdc.start_acq()
    print ("Sending pulses of period 8 ms to each TDC STOP channel")

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

#-----> Sending pulses to all the channels
    for i in range(4): # for the 4 pairs of channels (1,2), (1,3), (1,4), (1,5)

        otherch                   = i+1
        r_edge_timestamps_ch1     = []
        r_edge_timestamps_otherch = []
        all_measurs               = []

        # USB-interconnection-box
        box.select_trigger_loopback(0)
        box.select_output(4-i)

        # 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])
                elif (timestamps[m][2] == 1) and (timestamps[m][1] == otherch): # rising tstamps pair channel
                    r_edge_timestamps_otherch.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*(i+1):
            msg = ("ERROR: Unexpected error with interrupts; expected overflows: %d; received overflows: %d")%((NUM_OF_TSTAMPS*(i+1)),tdc_overflows_now)
            print (msg)
            all_msg+=msg

        # data manipulation for channel 1
        if i == 0:
            print "\n_________________________TDC_STOP%d test____________________________\n" %(i+1)
            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)
                    all_msg+=msg
                else:
                    avg = ((sum(measurs_ch1, 0.0) / len(measurs_ch1))/1e9) # in ms
                    print "average          : %.2f ms"%(avg)
                    print "span             : %.2f ps"%((max(measurs_ch1))-(min(measurs_ch1)))
                    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)))
                    print ("\nACAM Ch1 OK! Received %d timestamps of average period %.2f ms\n\n")%(len(measurs_ch1), avg)

            else:
                msg = ("ERROR: Ch1 registered timestamps: %d; expected: around %d \n")%(len(r_edge_timestamps_ch1), 64*NUM_OF_TSTAMPS)
                print (msg)
                all_msg+=msg

        # data manipulation for the other channel
        print "_________________________TDC_STOP%d test____________________________\n" %(i+2)
        print "Ch%d tstamps      : %d"%(i+2, len(r_edge_timestamps_otherch))
        print "TDC write pointer: %d"%(tdc_write_pointer_now)
        print "TDC overflows    : %d"%(tdc_overflows_now)
        measurs_otherch = []
        if len(r_edge_timestamps_otherch) >  63*NUM_OF_TSTAMPS:
            r_edge_timestamps_otherch.sort()
            for k in range(0, len(r_edge_timestamps_otherch)-1, 1):
                measur = r_edge_timestamps_otherch[k+1] - r_edge_timestamps_otherch[k]
                if (measur < 8999995000) or (measur < 9000005000):
                    measurs_otherch.append (measur)

            print "samples          : %d"%(len(measurs_otherch))
            # evaluate the timestamps for Ch1
            if (len(measurs_otherch) < 630) or (len(measurs_otherch) > 650):
                msg = ("ERROR: Ch%d received %d timestamps of 8ms period; expected: around %d \n")%(i+2, len(measurs_otherch), 64*NUM_OF_TSTAMPS)
                print (msg)
                all_msg+=msg
            else:
                avg = ((sum(measurs_otherch, 0.0) / len(measurs_otherch))/1e9) # in ms
                print "average          : %.2f ms"%(avg)
                print "span             : %.2f ps"%((max(measurs_otherch))-(min(measurs_otherch)))
                print "max iter %d %.2f ps"%(measurs_otherch.index(max(measurs_otherch)), (max(measurs_otherch)))
                print "min iter %d %.2f ps"%(measurs_otherch.index(min(measurs_otherch)),(min(measurs_otherch)))
                print ("\nACAM Ch%d OK! Received %d timestamps of average period %.2f ms\n\n")%(i+2, len(measurs_otherch), avg)

        else:
            msg = ("ERROR: Ch%d registered timestamps: %d; expected: around %d \n")%(i+2, len(r_edge_timestamps_otherch), 64*NUM_OF_TSTAMPS)
            print (msg)
            all_msg+=msg


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


    print "\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()
