#!   /usr/bin/env   python

# Copyright Creotech SA, 2015
# Author: Marek Guminski <marek.guminski@creotech.pl>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org


import sys
import time
import os
import csr
import time



from spec import *
from ad9516 import *
from ad9510 import *
from dac import *
from ptsexcept import *
from sys import __stdout__

import calibr_box
import find_usb_tty
import calibration_box_connections

from spec_identification import *

"""
test03: 
    AD9510 tests
        SPI check
        Function (SYNCB) line
        CLK2OUT
        CLK_VCXO (Beam out)
        RF_IN
        REF OUT (connector output of DAC)
        STATUS
        REF_IN
        CLK_PD  
       
        
"""



FIRMWARE_PATH = '/../gateware/syn/spec_top.bin'

AUTOMATION = True
TEST_RAW_DAC = False


def spi_test(dut):
    
    # AD9510 doesn't have any status/ID registers
    # to verify SPI I have to write/read some registers
    
    written = 0xFF;
    dut.write_reg(0x49, written);
    read = dut.read_reg(0x49);
    
    if written != read:
        raise PtsError("AD9510: SPI communication FAILED on 0xFF")
    
    time.sleep(0.001)
    
    written = 0x00;
    dut.write_reg(0x49, written);
    read = dut.read_reg(0x49);
    
    if written != read:
        raise PtsError("AD9510: SPI communication FAILED on 0x00")
    
    time.sleep(0.001)
    
    written = 0xAA;
    dut.write_reg(0x49, written);
    read = dut.read_reg(0x49);
    
    if written != read:
        raise PtsError("AD9510: SPI communication FAILED on 0xAA")
    
    time.sleep(0.001)
    
    written = 0x55;
    dut.write_reg(0x49, written);
    read = dut.read_reg(0x49);
    
    if written != read:
        raise PtsError("AD9510: SPI communication FAILED on 0x55")
    
    print "AD9510: SPI test succeeded"
    
# mast be called after SPI test
def test_function_line( dut ):
    
    # I assume SPI was verified
    
    # enable reset
    dut.gpio_set(dut.PIN_FUNCTION, 0)
    
    # SPI should fail if Function line (reset_n) is connected
    try:
        spi_test(dut);
    except PtsError:
        print "AD9510: Function line was verified"
    
    # disable reset
    dut.gpio_set(dut.PIN_FUNCTION, 1)    
    
    time.sleep(0.001)
    
    # just in case: test SPI again
    spi_test(dut)
    

def test_ld_line(dut):
    
    
    # I assume that SPI communication was verified
    # To check STATUS line I program it for the same function but with other polarization
    # If the line doesn't change value then it might be corrupted (I assume that indicated value didn't change in the meantime)
    # I will measure REF detect because it shouldn't change rapidly
            
    # Loss of reference (active low)
    dut.write_reg(
        0x08,
        ( PFD_POLARITY(0) | PLL_MUX_ON_STATUS( 0xF ) | CP_MODE(0) )
        );
    
    # Update registers 
    dut.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    # Delay
    time.sleep(0.05)
    low = 1 if dut.gpio_get(dut.PIN_STATUS) > 0 else 0;

    # Loss of reference (active high)
    dut.write_reg(
        0x08,
        ( PFD_POLARITY(0) | PLL_MUX_ON_STATUS( 0xA ) | CP_MODE(0) )
        );
    
    # Update registers
    dut.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    # Delay
    time.sleep(0.05)    
    high = 1 if dut.gpio_get(dut.PIN_STATUS) > 0 else 0;

    # If loss of reference didn't change
    # then low and high should differ (doesn't matter if reference is valid or not)
    # It only matters if reference doesn't show up or fade in meantime
    # DAC output is REF input for PLL and it didn't change 
    if low == high:
        raise PtsError("AD9510: STATUS line test failed. This line might be corrupted")
    else:
        print "AD9510: STATUS line verification succeeded"

def test_refclk_presense(dut):
    
    
    # I assume that SPI communication was verified
    # PLL can detect loss of reference 
    
    # Loss of reference (active low)
    dut.write_reg(
        0x08,
        ( PFD_POLARITY(0) | PLL_MUX_ON_STATUS( 0xF ) | CP_MODE(0) )
        );
    
    # Update registers 
    dut.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    # Delay
    time.sleep(0.05)
    result = 1 if dut.gpio_get(dut.PIN_STATUS) > 0 else 0;

  
    if result == 0:
        raise PtsError("AD9510: REFIN not detected. Connection between DAC and VXCO_PLL might be corrupted.")
    else:
        print "AD9510: REFIN line verification succeeded"

def verify_fpll_clk2( carrier ):
    lock = carrier.readl(DDS_REG_FPGAPLL_CLK2_STAT)
    
    print "CLK2 FPGA PLL lock status: " + str( carrier.readl(DDS_REG_FPGAPLL_CLK2_STAT) )

    if lock != 0:
        return 1
    else:
        # rise reset signal for pll
        carrier.writel(DDS_REG_FPGAPLL_CLK2_CTRL, 1 )
        # wait 
        time.sleep(0.5)

        # remove reset signal for pll
        carrier.writel(DDS_REG_FPGAPLL_CLK2_CTRL, 0 )

        # wait for pll to lock
        for i in range(0,30):
            lock = carrier.readl(DDS_REG_FPGAPLL_CLK2_STAT)
            if lock != 0:
                print "PLL locked in %d try" % (i + 1)
                return 1
            time.sleep(0.5)

        # failed to lock
        return 0

def test_clk2(carrier, set_freq = 50e6, max_diff = 5e6 ):
    samples = 10;
#     measurement might not be very accurate
#     its just to check if clock lines are valid
    
    
    # PLL tends to loose synchronization when clock source is changed
    if verify_fpll_clk2( carrier ) == 0 :
        raise PtsError("CLK2 FPGA PLL did not lock on reference clock");
    
    time.sleep(3)

    mean = 0
    for i in range(samples):
        mean += carrier.readl(DDS_REG_CLK2)
        time.sleep(0.5)
        
    mean /= samples
    
    print "Measured frequency:%d" % mean
    
    if abs( set_freq-mean ) > max_diff :
        return 1
    else:
        return 0


def test_clk0(carrier, set_freq = 50e6, max_diff = 5e6 ):
    samples = 10;
#     measurement might not be very accurate
#     its just to check if clock lines are valid

    mean = 0
    for i in range(samples):
        mean += carrier.readl(DDS_REG_CLK0)
        time.sleep(0.5)
        
    mean /= samples
    
    print "Measured frequency:%d" % mean
    
    if abs( set_freq-mean ) > max_diff :
        return 1
    else:
        return 0


   
def test_rf_in(carrier, pll,dacinst, box):
    
    # I assume that raw dac output was tested by ADC
    # i also assume that CLK2 lines were already tested
    # if that is the case then if i connect this DAC output to RF IN
    # i can pass this clock through PLL and measure frequency in FPGA
    
    test_freq = 15e6;
    
    if not AUTOMATION:
        print >> __stdout__, "Please connect DAC output to RF IN input in FMC DAC 600M 12b 1cha DDS and confirm with enter button" 
        raw_input("")
        print >> __stdout__, "Proceeding"
    else:
        box.select_output(calibration_box_connections.connections['raw_dac'])
        time.sleep(1);
        
    print "Testing RF IN port with %d MHz frequency clock" % (test_freq/1e6)
    
    # Set DAC to output 10M Sine 
    dacinst.set_en( 1 );
    dacinst.set_shape( dacinst.SHAPE_SINE );
    dacinst.set_ampl( 1 );
    dacinst.set_frequency( test_freq );

    time.sleep(2);
    

    # make sure that dac works correctly
    if test_clk0( carrier, test_freq, 1e6) > 0:
        raise PtsError("DAC does not generate set frequency");
    else :
    	print "DAC output was verified on CLK0OUT"

    # set pll to pass CLK1 to OUT6 without frequency division
    pll.write_reg(
        0x0A,
        ( B_BYPASS(0) | PRESCALLER(0) | POWER_DOWN(0) )
        );
    
    pll.write_reg( 0x54, 0 );
            
    # divider bypassed
    pll.write_reg(
        0x55,
        ( BYPASS(1) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        # ( BYPASS(0) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        );
    
    pll.write_reg(
        0x45,
        ( SEL_CLK_IN(1) | CLK1_PD(0) | CLK2_PD( 0 ) |
            CLK_PLL_PD( 0 ) | REFIN_PD(0) | CLKS_IN_PD(0) )
        );
    
    # update configuration
    pll.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );    

    # wait for configuration to take effect
    time.sleep(3)
    
    # measure frequency in fpga
    if test_clk2(carrier, test_freq, 1e6) > 0:
        print "RF IN input might be corrupted"
        raise PtsError("RF IN input might be corrupted")
    
def test_ref_out(carrier, pll,dacinst, box):
    
    # I assume that RF IN input was already tested
    # i also assume that CLK2 lines were already tested
    # if that is the case then if i connect this output to RF IN
    # i can pass this clock through PLL and measure frequency in FPGA
    
    if not AUTOMATION:
        print >> __stdout__, "Please connect REF CLK output to RF IN port in FMC DAC 600M 12b 1cha DDS and confirm with enter button" 
        raw_input("")
    else:
        box.select_output(calibration_box_connections.connections['ref_clk'])
        time.sleep(1);
        
    test_freq = 20e6;
    
    print "Testing REF CLK port with %d MHz frequency clock" % ( test_freq/1e6 )
    # Set DAC to output 10M Sine
    dacinst.set_en( 1 );
    dacinst.set_shape( dacinst.SHAPE_SINE );
    dacinst.set_ampl( 1 );
    dacinst.set_frequency( test_freq );
    
    time.sleep(3)

    # make sure that dac works correctly
    if test_clk0( carrier, test_freq, 1e6) > 0:
        raise PtsError("DAC does not generate set frequency");
    else:
        print "DAC output was verified on CLK0OUT"


    # set pll to pass CLK1 to OUT6 without frequency division
    pll.write_reg(
        0x0A,
        ( B_BYPASS(0) | PRESCALLER(0) | POWER_DOWN(0) )
        );
    
    pll.write_reg( 0x54, 0 );
            
    # divider bypassed
    pll.write_reg(
        0x55,
        ( BYPASS(1) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        # ( BYPASS(0) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        );
    
    pll.write_reg(
        0x45,
        ( SEL_CLK_IN(1) | CLK1_PD(0) | CLK2_PD( 0 ) |
            CLK_PLL_PD( 0 ) | REFIN_PD(0) | CLKS_IN_PD(0) )
        );
    
    # update configuration
    pll.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    # wait for configuration to take effect
    time.sleep(5)
    
    # measure frequency in FPGA
    if test_clk2(carrier, test_freq, 1e6) > 0:
        print "REF CLK output might be corrupted"
        raise PtsError("REF CLK output might be corrupted")
    
def test_beam_out(carrier, pll,dacinst, box):
    
    # I assume that RF IN input was already tested
    # i also assume that CLK2 lines were already tested
    # if that is the case then if i connect Beam out to RF IN
    # i can pass this clock through PLL and measure frequency in FPGA
    if not AUTOMATION:   
        print >> __stdout__, "Please connect Beam CLK output to RF IN port in FMC DAC 600M 12b 1cha DDS and confirm with enter button" 
        raw_input("")
        
    else:
        box.select_output(calibration_box_connections.connections['beam_clk'])
        time.sleep(5);
   
    test_freq = 50e6

    print "Testing Beam CLK port with %d MHz frequency clock" % ( test_freq/1e6)
    # set pll to pass CLK1 to OUT6 with 10x frequency division
    pll.write_reg(
        0x0A,
        ( B_BYPASS(0) | PRESCALLER(0) | POWER_DOWN(1) )
        );
    
    pll.write_reg( 0x54, 4 | 4<<4 );
            
    # divider bypassed
    pll.write_reg(
        0x55,
        ( BYPASS(0) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        );
    
    pll.write_reg(
        0x45,
        ( SEL_CLK_IN(1) | CLK1_PD(0) | CLK2_PD( 0 ) |
            CLK_PLL_PD( 0 ) | REFIN_PD(0) | CLKS_IN_PD(0) )
        );
        
    # update configuration    
    pll.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    time.sleep(5)
    
    # measure frequency in FPGA
    # BEAM should be 500MHz divided by 10 in output divider
    if test_clk2(carrier, test_freq, 2e6) > 0:
        print "BEAM CLK output might be corrupted"
        raise PtsError("BEAM CLK output might be corrupted")



def main (card=None, default_directory='.',suite=None):

    if AUTOMATION:
        BOX_USB_VENDOR_ID = 0x10c4 # Cygnal Integrated Products, Inc.
        BOX_USB_DEVICE_ID = 0xea60 # CP210x Composite Device

        print("Looking for calibration box")

        box_tty = find_usb_tty.CttyUSB().find_usb_tty(BOX_USB_VENDOR_ID, BOX_USB_DEVICE_ID)

        if len( box_tty ) < 1:
            raise PtsCritical( "Can't find calibration box. Make sure that it is connected, and that CP210x driver was correctly mounted");
        
        # print("Calibration Box device: %s" % box_tty[0])
        box = calibr_box.CCalibr_box(box_tty[0])
    
        box.select_trigger_loopback(0)
        box.select_output(calibration_box_connections.connections['ref_clk'])
        
    else:
        box = 0;
    
    specid = spec_identification()[0];
    if specid < 0 :
        raise PtsCritical("Bus number of FmcDac600m12b1chadds was not found")

    carrier=spec( FIRMWARE_PATH, specid, True );
    sys_pll = ad9516(carrier);
    
    # default configuration
    vcxo_pll = ad9510(carrier, 1);
    
    dacinst = dac(carrier);
    
    # check SPI communication
    spi_test(vcxo_pll);
    
    # check function line
    test_function_line(vcxo_pll);
    
    # default configuration after reset
    vcxo_pll = ad9510(carrier, 1);
      

    # check status line
    test_ld_line(vcxo_pll);

    # check REFIN lineDDS_DELAY_VALUE_W( 0 ) | DDS_DELAY_ENABLE()
    # first I have to make sure DAC is enabled    
    dacinst = dac(carrier);
    test_refclk_presense(vcxo_pll);
    time.sleep(2)
    
    # validate CLK2 lines to FPGA
    print "Testing CLK2 line with 50 MHz frequency clock"
    if test_clk2(carrier, 50e6, 5e6) > 0:
        raise PtsError("ad9510: CLK2 clock lines might be corrupted")
    print "CLK2 line verification succeeded"
    
    
    if TEST_RAW_DAC:
        # validate RF IN input connector
        test_rf_in(carrier, vcxo_pll, dacinst, box)
    
    # validate REF OUT output connector
    test_ref_out(carrier, vcxo_pll, dacinst, box)
       
         
    vcxo_pll = ad9510(carrier, 1);
    # validate BEAM OUT output connector
    test_beam_out(carrier, vcxo_pll, dacinst, box)
    
    return 0
        
if __name__ == '__main__' :
    main()

