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

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                                       TDC_IN_FPGA test                                         |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Testing of the TDC_IN_FPGA lines of the TDC board.                                |
##                                                                                                |
##              In order to simplify the testing procedure, this test uses the same setup that is |
##              used for the TDC calibration (*).                                                 |
##              The Pendulum serves as the pulse generator; it is programmed to send pulses of    |
##              100 ms pulse width, 500 ms period.                                                |
##              The output of the Pendulum is fed to a Timetech Fanout unit, from where we use    |
##              two outputs.                                                                      |
##              The USB-interconnection-box is used for the distribution of the two Timetech      |
##              Fanout outputs to the five TDC input channels.                                    |
##              The USB-interconnection-box channel 1 is constantly connected to the              |
##              TDC-under-test channel 1. The other Timetech Fanout output can be sent to any of  |
##              the TDC-under-test channels 2, 3, 4, 5, according to the command sent to the box. |
##                                                                                                |
##              During the test we send pulses to each one of the TDC channels; inside the FPGA,  |
##              a dedicated counter per channel is running at 100 MHz (=cycles of 10 ns) and is   |
##              counting the number of cycles when the corresponding channel is at '1'.           |
##              For each channel, the Pendulum stays ON for one second; with 100 ms pulse width   |
##              and 500 ms period, we are expecting two 100 ms pulses to arrive to the FPGA.      |
##              Therefore, inside the FPGA, a counter is expected to count 20 M cycles.           |
##              Note however that because of the nature of the USB-interconnection-box, we cannot |
##              be sending pulses to all the channels simultaneously, but only to the pairs:      |
##              (ch 1, ch 2), (ch 1, ch 3), (ch 1, ch 4), (ch 1, ch 5).                           |
##              This means that channel 1 is expected to register 4 times more cycles that the    |
##              rest of the channels, that is 80 M.                                               |
##              Note also that he channels-enable and terminations-enable signals should be       |
##              functional for the test to succeed.                                               |
##              Note finally that for this test none of the ACAM logic is used.                   |
##                                                                                                |
##              The counter for TDC_IN_FPGA_1 can be accessed at PCIe address x00080              |
##              The counter for TDC_IN_FPGA_2 can be accessed at PCIe address x00084              |
##              The counter for TDC_IN_FPGA_3 can be accessed at PCIe address x00088              |
##              The counter for TDC_IN_FPGA_4 can be accessed at PCIe address x0008C              |
##              The counter for TDC_IN_FPGA_5 can be accessed at PCIe address x00090              |
##                                                                                                |
##              The channels and terminations enable can be accessed at PCIe address x000A0:      |
##                bit 0: ENABLE_INPUTS                                                            |
##                bit 1: TERM_EN_1                                                                |
##                bit 2: TERM_EN_2                                                                |
##                bit 3: TERM_EN_3                                                                |
##                bit 4: TERM_EN_4                                                                |
##                bit 5: TERM_EN_5                                                                |
##                                                                                                |
##              (*) For this test it would have been more straight-forward to use five outputs    |
##               of the Timetech Fanout unit; however, not to change the setup that is essential  |
##               for the calibration tests that follow, we are only using two channels and the    |
##               USB-interconnection-box.                                                         |
##                                                                                                |
## FW to load   tdc_pts_input_pulse.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.5")
    meas.write("SOUR:PULS:WIDT 0.1")
    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


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_pts_input_pulse.bit'
    FPGA_LOADER_PATH        = '../../../../pts/gnurabbit/user/fpga_loader'

#-----> FPGA constants
    # Addressing for the retrieval of carrier csr information
    CARRIER_CSR          = 0x00000 # base address
    CSR_TYPE_VER         = 0x00
    CSR_BSTM_TYPE        = 0x04
    CSR_BSTM_DATE        = 0x08
    CSR_STATUS           = 0x0C
    CSR_CTRL             = 0x10
    PCB_VER_MASK         = 0x000F
    CARRIER_TYPE_MASK    = 0xFFFF0000
    STATUS_FMC_PRES      = (1<<0)
    # Addressing for the channels/termination enable
    INPUT_EN             = 0xA0
    # Addressing for the counters
    CHANNEL_1_COUNTER    = 0x80
    CHANNEL_2_COUNTER    = 0x84
    CHANNEL_3_COUNTER    = 0x88
    CHANNEL_4_COUNTER    = 0x8C
    CHANNEL_5_COUNTER    = 0x90
    CHANNEL_COUNTERS     = [0x80, 0x84, 0x88, 0x8C, 0x90]


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

    # Bind SPEC object to FMC TDC card
    print "\n-------------------------------------------------------------------"
    print "----------------------------- FMC TDC -----------------------------"
    print "------------------------- TDC_IN_FPGA test ------------------------\n"
    print ("Test of the TDC_IN_FPGA[5..1] signals. The test is using the\n"
           "Pendulum as pulse generator as well as the Timetch Fanout and the\n"
           "USB-interconnection-box for the distribution of the pulses to all\n"
           "the TDC input channels.\n"
           "Dedicated counters in the carrier FPGA are counting the duration\n"
           "of the pulses.\n"
           "Note that for this test none of the ACAM logic is used.\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)
    carrier_csr = csr.CCSR(spec, CARRIER_CSR)
    tdc         = fmc_tdc.CFMCTDC(spec)

    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)
    os.system(firmware_loader + ' ' + bitstream )
    time.sleep(1)
    print "Firmware loaded!"

    # Check bitsteam type
    bitstream_type = carrier_csr.rd_reg(CSR_BSTM_TYPE)
    print('Bitstream type     :%.8X') % bitstream_type
    if(bitstream_type == 0xFFFFFFFF):
        msg = ("FATAL ERROR: Wrong bitstream type loaded")
        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) 


    # Carrier Information
    print "\n___________________________Carrier info__________________________\n"
    print("PCB version        : %d") % (PCB_VER_MASK & carrier_csr.rd_reg(CSR_TYPE_VER))
    print("Carrier type       : %d") % ((CARRIER_TYPE_MASK & carrier_csr.rd_reg(CSR_TYPE_VER))>>16)
    print("Bitstream type     : 0x%.8X") % (carrier_csr.rd_reg(CSR_BSTM_TYPE))
    print("Bitstream date     : 0x%.8X") % (carrier_csr.rd_reg(CSR_BSTM_DATE))
    print("Status             : 0x%.8X") % (carrier_csr.rd_reg(CSR_STATUS))
    print("Control            : 0x%.8X") % (carrier_csr.rd_reg(CSR_CTRL))

    # Check mezzanine presence flag
    print "\n__________________________Presence test__________________________\n"
    status = carrier_csr.rd_reg(CSR_STATUS)
    if(status & STATUS_FMC_PRES):
        print('Carrier csr status :%.8X') % status
        msg = ("ERROR: Mezzanine appears missing.")
        os.close(spec.fd)
        print (msg)
        raise PtsCritical (msg)

    else:
        print("Mezzanine present OK\n")

#-----> TDC setup
    # Enable input logic
    # Channels enable activated
    # Termination enable of all the channels activated;
    carrier_csr.wr_reg(INPUT_EN, 0x3F)

    # Check FPGA counters
    counter_error = 0 
    for i in CHANNEL_COUNTERS:
        if (carrier_csr.rd_reg(i) != 0):
            msg = (("ERROR: Unexpected failure of FPGA counter 0x%.2X; received 0x%.8X, expected 0x00000000")%(i, carrier_csr.rd_reg(i)))
            print (msg)
            raise PtsError (msg)
            counter_error = 1
    if (counter_error == 0):
        print("Counters init      : OK")

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


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

        # pendulum sending pulses
        time.sleep(1.2)

        # pendulum output off
        a = pendulum_output_off(meas)

#-----> Results
    print "\n___________________________Results_______________________________\n"

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

    # Read counters
    for i in CHANNEL_COUNTERS:
        if (carrier_csr.rd_reg(i) == 0):
            msg = (("ERROR: TDC_IN_FPGA%d: No pulse arrived ")%(CHANNEL_COUNTERS.index(i)+1))
            print (msg)
            all_msg+=msg

        # channel 1 should have registered 4 times more cycles
        elif (((CHANNEL_COUNTERS.index(i)+1)== 1) and ((round(carrier_csr.rd_reg(i)/1e6) > 4* 30) or (round(carrier_csr.rd_reg(i)/1e6) < 4*10))):
            msg = (("ERROR: TDC_IN_FPGA%d: Counted %dM cycles; expected 80M")%(CHANNEL_COUNTERS.index(i)+1, round(carrier_csr.rd_reg(i)/1e6)))
            print (msg)
            all_msg+=msg

        # rest of channels
        elif (((CHANNEL_COUNTERS.index(i)+1) > 1)  and ((round(carrier_csr.rd_reg(i)/1e6) > 30) or (round(carrier_csr.rd_reg(i)/1e6) < 10))): # 0.5% acceptance limit
            msg = (("ERROR: TDC_IN_FPGA%d: Counted %dM cycles; expected 20M ")%(CHANNEL_COUNTERS.index(i)+1, round(carrier_csr.rd_reg(i)/1e6)))
            print (msg)
            all_msg+=msg

        else:
            print("TDC_IN_FPGA%d: Counted %dM cycles OK")%(CHANNEL_COUNTERS.index(i)+1, round(carrier_csr.rd_reg(i)/1e6))

    if (all_msg != ""):
        raise PtsError (all_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()
