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

##-------------------------------------------------------------------------------------------------
##                                                                                                |
##                               TDC ENABLE_INPUTS test with FPGA                                 |
##                                                                                                |
##-------------------------------------------------------------------------------------------------
##                                                                                                |
## Description  Testing of the ENABLE_INPUTS signal of the TDC board.                             |
##                                                                                                |
##              The test uses the Fine Delay as pulse genrator. Pulses are sent to all the        |
##              inputs with the enable activated and then deactivated.                            |
##              Through the TDC_IN_FPGA signals we monitor the arrival of the pulses.             |
##              Dedicated counters in the FPGA register the input pulses.                         |
##              Note 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 counters reset            can be accessed at PCIe address x00024              |
##                                                                                                |
##              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                                                                |
##                                                                                                |
##                                                                                                |
## 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
import pylab
from pylab import *
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




def main (default_directory='.'):

    # Constants declaration
    FMC_TDC_ADDR             = '1a39:0004/1a39:0004@0001:0000'#'1a39:0004/1a39:0004@000B:0000'
    FMC_TDC_BITSTREAM_PATH   = '../firmwares/tdc_pts_input_pulse.bit'
    FMC_DELAY_ADDR           = '018d:0000/018d:0000@0002:0000'#'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

    # 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
    RST_COUNTERS         = 0x24
    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]

   
    # SPEC object declaration
    spec = rr.Gennum()

#-----> TDC
    # Bind SPEC object to FMC TDC card
    print "\n-------------------------------------------------------------------"
    print "----------------------------- FMC TDC -----------------------------"
    print "--------------------- TDC_IN_FPGA disable test --------------------\n"
    print ("Test of the EN_INPUTS signal. The test is using the Fine Delay board\n"
           "as pulse generator, sending pulses to each one of the TDC channels.\n"
           "Dedicated counters in the carrier FPGA are counting the duration of the pulses.\n"
           "When the EN_INPUTS is active, pulses should be arriving to all the channels;\n"
           "when the EN_INPUTS is disabled no pulse should arrive.\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)
    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!"

    # Check bitsteam type
    bitstream_type = carrier_csr.rd_reg(CSR_BSTM_TYPE)
    if(bitstream_type == 0xFFFFFFFF):
        msg = ("FATAL ERROR: Firmware not properly 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)

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

    status = carrier_csr.rd_reg(CSR_STATUS)
    if(status & STATUS_FMC_PRES):
        msg = ('FATAL ERROR: Mezzanine appears missing.')
        os.close(spec.fd)
        print (msg)
        raise PtsCritical  (msg)
    else:
        print("Mezzanine present  : OK")

    # 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)))
            os.close(spec.fd)
            print (msg)
            raise PtsError (msg)
            counter_error = 1
    if (counter_error == 0):
        print("Counters init      : OK")


    ################################## Input logic Disabled ##################################
    carrier_csr.wr_reg(INPUT_EN, 0x00)


#-----> Fine Delay
    print "\n___________________________Fine Delay____________________________"
    print "\nFine Delay initializations.."
    # Bind SPEC object to FMC Fine Delay card
    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
    enable         = 1
    t_start_coarse = 0
    width          = 1000000000 # pulse width 1 ms
    delta          = 2000000000
    count          = 1
    t_start_utc    = fdelay.get_time().utc+1 # starting in  1 sec
    fdelay.conf_pulsegen(1, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(2, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(3, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(4, enable, t_start_utc, t_start_coarse, width, delta, count)

    print ("\nFine delay sending one pulse per channel of 1ms width..") 

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

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

    print "\n_____________________Input logic Disabled________________________\n"

    # Read counters
    for i in CHANNEL_COUNTERS:
        if (carrier_csr.rd_reg(i) != 0):
            msg = (("ERROR: ENABLE_INPUTS disabled: TDC_IN_FPGA%d failure; pulse of %.5f ms arrived ")%(CHANNEL_COUNTERS.index(i)+1,carrier_csr.rd_reg(i)/1e5))
            print (msg)
            all_msg+=msg
        else:
            print("ENABLE_INPUTS disabled: TDC_IN_FPGA%d OK") %(CHANNEL_COUNTERS.index(i)+1)


    ################################## Input logic Enabled ##################################
    carrier_csr.wr_reg(INPUT_EN, 0x1F)
    carrier_csr.wr_reg(RST_COUNTERS,0x1)
    time.sleep(0.1)
    carrier_csr.wr_reg(RST_COUNTERS,0x0)


#-----> Fine Delay
    spec.bind(FMC_DELAY_ADDR)
    t_start_utc    = fdelay.get_time().utc+1 # starting in  1 sec
    fdelay.conf_pulsegen(1, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(2, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(3, enable, t_start_utc, t_start_coarse, width, delta, count)
    fdelay.conf_pulsegen(4, enable, t_start_utc, t_start_coarse, width, delta, count)

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

    print "\n______________________Input logic Enabled________________________\n"

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

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

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

if __name__ == '__main__' :
    main()
