##_______________________________________________________________________________________________
##
##                                      CONV-TTL-RS485 PTS
##
##                                         CERN,BE/CO-HT
##_______________________________________________________________________________________________
##
##-----------------------------------------------------------------------------------------------
##
##                                     CONV-TTL-RS485 LEDs test
##
##-----------------------------------------------------------------------------------------------
##
## Description  Testing the rear-transition module interface of the CONV-TTL-RS485 front module
##              board. This is done in conjunction with gateware implemented on the
##              CONV-TTL-RS485 FPGA.
##
##              The gateware implements pulse repeaters which are controlled by this script to
##              send pulses back to inputs. Externally to the RTM, there should be two boards
##              containing differential multiplexers, and these are controlled by the FPGA HDL
##              through this script such that each channel sends pulses back to itself, in the
##              sequence Out1->In, Out2->In, Out3->In.
##
##              In short, what this script does is to turn on pulse repetition, sleep for a
##              predefined amount of time, and then check that the same number of pulses that has
##              been sent at the channel output are received at its input. The checking is done by
##              reading the input and output channel pulse counters, implemented in the FPGA HDL.
##              Checking is done with input and output terminations ON and OFF (one run of the same
##              test sequence for both).
##
## Authors      Julian Lewis (Julian.Lewis@cern.ch)
##              Theodor-Adrian Stana (t.stana@cern.ch)
## Website      http://www.ohwr.org/projects/pts
## Date         11/11/2014
##-----------------------------------------------------------------------------------------------
##
##------------------------------------------------------------------------------------------------
##                               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 system modules
import sys
sys.path.append("pyts")
import time

# Import common modules
from ptsexcept import *
from vv_pts import *
from ptsdefine import *

#--------------------------------------------------------------------------------------------------
# Pulse counter class
#--------------------------------------------------------------------------------------------------
class CPulseCounter:

    def __init__(self, bus, base):
        self.bus  = bus
        self.base = base

    def wr_reg(self, addr, val):
        self.bus.vv_write(self.base + addr,val)

    def rd_reg(self, addr):
        return self.bus.vv_read(self.base + addr)

    def wr_out_cnt(self, chan, val):
        return self.wr_reg((chan-1)*8, val)

    def wr_in_cnt(self, chan, val):
        return self.wr_reg((chan-1)*8 + 4, val)

    def rd_out_cnt(self, chan):
        return self.rd_reg((chan-1)*8)

    def rd_in_cnt(self, chan):
        return self.rd_reg((chan-1)*8 + 4)

#--------------------------------------------------------------------------------------------------
# Test sequences
#--------------------------------------------------------------------------------------------------
def mux_sel(bus, sel):
    val = bus.vv_read(CSR)
    val &= ~(1 << CSR_TSTCS0_OFS)
    val &= ~(1 << CSR_TSTCS1_OFS)
    if (sel & 0x1):
        val |= (1 << CSR_TSTCS0_OFS)
    if (sel & 0x2):
        val |= (1 << CSR_TSTCS1_OFS)
    bus.vv_write(CSR, val)
    time.sleep(0.1)

def en_pulse_gen(bus):
    val = bus.vv_read(CSR)
    val |= (1 << CSR_REARPT_OFS)
    bus.vv_write(CSR, val)
    time.sleep(0.1)

def dis_pulse_gen(bus):
    val = bus.vv_read(CSR)
    val &= ~(1 << CSR_REARPT_OFS)
    bus.vv_write(CSR, val)
    time.sleep(0.1)

def clear_counters(bus):
    pc = CPulseCounter(bus, PULSE_CNT_BASE)
    # Clear pulse counters for the TTL channels
    for i in range(11, 17):
        pc.wr_out_cnt(i, 0)
        pc.wr_in_cnt(i, 0)

def check_counters(bus, inf, pel):
    pc = CPulseCounter(bus, PULSE_CNT_BASE)
    ic_arr = []
    oc_arr = []
    for i in range(11, 17):
        ic_arr.append(pc.rd_in_cnt(i))
        oc_arr.append(pc.rd_out_cnt(i))

    # First, check for all-zeroes in counters, indicating enable NAND gate failure
    if all(ic == 0 for ic in ic_arr):
        msg = "ERROR: No pulses received - check tester board connection, or enable NAND gate IC8"
        pel.set(msg)
    else:
        for i in range(len(ic_arr)):
            if (ic_arr[i] == oc_arr[i]):
                msg = "Ch%d input counter (%d) matches the output counter (%d) - good\n" % (i+1,
                        ic_arr[i], oc_arr[i])
                inf.write(msg)
            else:
                msg = "ERROR: Ch%d input counter (%d) does not match output counter (%d)"% (i+1,
                        ic_arr[i], oc_arr[i])
                pel.set(msg)

def pulse_test(bus, inf, pel, output, term):
    if (output == 1):
        disp = 3
    elif (output == 2):
        disp = 2
    elif (output == 3):
        disp = 1
    msg = "Testing channel connections (term. %s): O%d -> IN" % (term, disp)
    print(msg)
    mux_sel(bus, output)
    clear_counters(bus)
    inf.write('\n')
    inf.write(msg+'\n')
    en_pulse_gen(bus)
    time.sleep(2)
    dis_pulse_gen(bus)
    check_counters(bus, inf, pel)
    time.sleep(1)

##-------------------------------------------------------------------------------------------------
##                                             main                                              --
##-------------------------------------------------------------------------------------------------
def main(bus, tname, inf, log):

    """
    tests : RS-485 pulse repetition, RS-485 transceivers IC31-IC56,
            solid-state switches IC56-IC75, solid-state switches
            IC23-IC28, RS-485 transceivers IC16-IC21, Schmitt trigger
            inputs IC30, IC45, NAND gate IC8, IC69 Schmitt trigger for
            the RTM detection lines
    uses  : pts.bit and rs485_pulse_rtm.py
    """

    pel = PTS_ERROR_LOGGER(inf, log)

    try:

        # Read RTM lines
        rtm = (bus.vv_read(CSR) >> CSR_RTM_OFS) & 0x3f
        if (rtm == 0x09):
            msg = "RTM detection lines read correctly: 0x%02X" % rtm
            inf.write(msg+'\n')
        else:
            msg = "ERROR: RTM detection lines readout (0x%02X) incorrect - expected 0x09. Check RTM presence or IC69" % rtm
            pel.set(msg)
            return pel.get()

        # First, clear the pulse counters
        clear_counters(bus)

        # Ask the user to make the daisy-chain
        print("Connect the LEMO cable as follows:")
        print("  - BLU cable to INV-TTL CH A")
        print("  - GRN cable to INV-TTL CH B")
        print("  - RED cable to INV-TTL CH C")
        print("  - YEL cable to INV-TTL CH D")

        reply = raw_input("Have the connections been made? (yes/no) ")
        while (1):
            if "yes" in reply.lower():
                break
            if "no" in reply.lower():
                msg = "ERROR: Control connections to RS485 tester not made"
                pel.set(msg)
                return pel.get()
            else:
                reply = raw_input('Please type "yes" or "no" to continue: ')

        # Power on the tester card
        val = bus.vv_read(CSR)
        val |= (1 << CSR_TSTCVCC_OFS)
        bus.vv_write(CSR, val)
        time.sleep(1)

    #---------------------------
    # Test with terminations OFF
    #---------------------------
        term = "OFF"
        inf.write('\n')

        # Read out fail-safe lines -- should now be high, since the MUXes are
        # powered but not enabled
        msg = "Testing RS-485 fail-safe on open-connection case (term. %s)" % term
        print(msg)
        inf.write('\n')
        inf.write(msg+'\n')
        val = bus.vv_read(LSR)
        val >>= LSR_REARFS_OFS
        if (val == 0x3f):
            msg = "RS-485 failsafe lines read as expected: 0x%02X\n" % val
            inf.write(msg)
        else:
            msg = "ERROR: Failsafe lines readout (0x%02X) incorrect - expected 0x3F" % val
            pel.set(msg)

        # Enable multiplexer
        print("Enabling multiplexers")
        val = bus.vv_read(CSR)
        val |= (1 << CSR_TSTCMUXEN_OFS)
        bus.vv_write(CSR, val)
        time.sleep(1)

        # Fail-safe test
        msg = "Testing RS-485 fail-safe on short-circuit case (term. %s)" % term
        print(msg)
        mux_sel(bus, 0x0)
        inf.write('\n')
        inf.write(msg+'\n')
        val = bus.vv_read(LSR)
        val >>= LSR_REARFS_OFS
        if (val == 0x3f):
            msg = "RS-485 failsafe lines read as expected: 0x%02X\n" % val
            inf.write(msg)
        else:
            msg = "ERROR: Failsafe lines readout (0x%02X) incorrect - expected 0x3F" % val
            pel.set(msg)
        time.sleep(1)

        # Generate pulses & test correct reception
        pulse_test(bus, inf, pel, 1, term)

        pulse_test(bus, inf, pel, 2, term)

        pulse_test(bus, inf, pel, 3, term)

        # Disable multiplexer
        print("Disabling multiplexers")
        val = bus.vv_read(CSR)
        val &= ~(1 << CSR_TSTCMUXEN_OFS)
        bus.vv_write(CSR, val)
        time.sleep(1)

    #--------------------------
    # Test with terminations ON
    #--------------------------
        term = "ON"
        inf.write('\n')

        msg = "Enabling input and output terminations"
        print(msg)
        inf.write(msg + '\n')
        bus.vv_write(TER, (0x3f << TER_OTERM_OFS) | (0x3f << TER_ITERM_OFS))
        time.sleep(2)

        # Read out fail-safe lines -- should now be high, since the MUXes are
        # powered but not enabled
        msg = "Testing RS-485 fail-safe on open-connection case (term. %s)" % term
        print(msg)
        inf.write('\n')
        inf.write(msg+'\n')
        val = bus.vv_read(LSR)
        val >>= LSR_REARFS_OFS
        if (val == 0x3f):
            msg = "RS-485 failsafe lines read as expected: 0x%02X\n" % val
            inf.write(msg)
        else:
            msg = "ERROR: Failsafe lines readout (0x%02X) incorrect - expected 0x3F" % val
            pel.set(msg)

        inf.write('\n')

        # Enable multiplexer
        print("Enabling multiplexers")
        val = bus.vv_read(CSR)
        val |= (1 << CSR_TSTCMUXEN_OFS)
        bus.vv_write(CSR, val)
        time.sleep(1)

        # Fail-safe test
        msg = "Testing RS-485 fail-safe on short-circuit case (term. %s)" % term
        print(msg)
        mux_sel(bus, 0x0)
        inf.write('\n')
        inf.write(msg+'\n')
        val = bus.vv_read(LSR)
        val >>= LSR_REARFS_OFS
        if (val == 0x3f):
            msg = "RS-485 failsafe lines read as expected: 0x%02X\n" % val
            inf.write(msg)
        else:
            msg = "ERROR: Failsafe lines readout (0x%02X) incorrect - expected 0x3F" % val
            pel.set(msg)
        time.sleep(1)

        # Generate pulses & test correct reception
        pulse_test(bus, inf, pel, 1, term)

        pulse_test(bus, inf, pel, 2, term)

        pulse_test(bus, inf, pel, 3, term)

        # Disable multiplexer
        print("Disabling multiplexers")
        val = bus.vv_read(CSR)
        val &= ~(1 << CSR_TSTCMUXEN_OFS)
        bus.vv_write(CSR, val)

        return pel.get()

    except BusException, e:
        raise PtsError("SKT Exception: %s" % e)

    except BusWarning, e:
        raise PtsError("SKT Warning: %s" % e)

    finally:
        val = bus.vv_read(CSR)
        val &= ~(1 << CSR_TSTCVCC_OFS)
        val &= ~(1 << CSR_TSTCMUXEN_OFS)
        val &= ~(1 << CSR_TSTCS0_OFS)
        val &= ~(1 << CSR_TSTCS1_OFS)
        bus.vv_write(CSR, val)
