#!   /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.                                |
##                                                                                                |
##              The test uses the Fine Delay as pulse genrator. A 1 ms pulse is sent to each TDC  |
##              channel and is being registered by the dedicated counters in the FPGA.            |
##              Each FPGA counter is running with 100 MHz clock, therefore around 1K cycles are   |
##              expected to be counted.                                                           |
##              The channels enable and terminations enable should be functional for the test to  |
##              succeed.                                                                          |
##              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 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
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@000B:0000'
    FMC_TDC_BITSTREAM_PATH   = '../firmwares/tdc_pts_input_pulse.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

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

   
    # SPEC object declaration
    spec = rr.Gennum()

#-----> TDC
    # 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 Fine Delay\n"
           "board 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")
    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)
    print('Bitstream type     : 0x%.8X') % bitstream_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))

    # Check mezzanine presence flag
    status = carrier_csr.rd_reg(CSR_STATUS)
    if(status & STATUS_FMC_PRES):
        print('Carrier csr status : 0x%.8X') % status
        msg = ("FATAL ERROR: Mezzanine appears missing.")
        os.close(spec.fd)
        print (msg)
        raise PtsCritical (msg)
    else:
        print("Mezzanine present  : OK")

    # Enable input logic
    # Channels enable activated
    # Termination enable of channels 1,2,3,4 activated;
    # channels 4 and 5 are receiving the same Fine Delay pulse, only ch 4 termination activated
    carrier_csr.wr_reg(INPUT_EN, 0x1F)

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

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

    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
        elif (carrier_csr.rd_reg(i) > 100500) or (carrier_csr.rd_reg(i) < 95000): # 0.5% acceptance limit
            msg = (("ERROR: TDC_IN_FPGA%d: Pulse width %.5f ms is outside accepted limits [1.005 ms .. 0.995 ms] ")%(CHANNEL_COUNTERS.index(i)+1, carrier_csr.rd_reg(i)/1e5))
            print (msg)
            all_msg+=msg

        else:
            print("TDC_IN_FPGA%d: Pulse width %.5f ms OK")%(CHANNEL_COUNTERS.index(i)+1, carrier_csr.rd_reg(i)/1e5)

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


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

if __name__ == '__main__' :
    main()
