#!   /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 time

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

from spec_identification import *

from utilFunctions import *
import numpy

"""
test05: 
    Delay line test
        250 Mhz (4ns) clock (CLK1) is outputed from FPGA through Delay line
        Delay line is connected with Pulse Output (set to input mode) with lemo-lemo cable
        Delayed clock (CLK2) is probed with CLK1
        Since both clocks are synchronous, probed signal should be constant '0' or '1'
            For some delay values both clocks edges will be nearly aligned resulting in variable output
            Dedicated logic was developed in order to detect such cases
        During the test delay value is modified from min to max with constant step
        Probed signal value are assigned to each delay value (with indication if signal was stable)
        Delay is possible in 10 ns range (bigger then clock period)
        Test is supposed to find probed signal edges, and evaluate tunning range
        
"""



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

def test_delay_line( carrier, dacinst ):

    nr              = 10;
    default_freq    = 10e6;
    freq_thd        = 1e3;
    
    

    carrier.writel(DDS_REG_DELAY, 0 );
    
    tmp = 0;
    for i in range( nr ): 
        tmp += int( carrier.readl(DDS_REG_TRIGGER) );
        time.sleep(0.1)
        
    tmp = int( tmp/nr );
    
    print "Trigger input frequency: %d Hz" % tmp
    
    if ( abs( tmp - default_freq ) > freq_thd ):
        raise PtsError( "Input frequency measured on Trigger input differ from correct value. This input might be corrupted" )
    else:
        print "Trigger input and pulse output verified successfully"


def phase_comp_maxval(carrier, value ):
#     this function overwrites delay line configuration
#     don't use after delay_write
    carrier.writel(DDS_REG_DELAY, DDS_DELAY_MAXVAL_W(value) );
    
    
def find_edges(value, stable, delay):


    edges=[]

    ST_UNDEF = -1
    ST_0 = 0
    ST_1 = 1
    state = ST_UNDEF

    for i in xrange(len(delay)):
        if state == ST_UNDEF:
            if stable[i]:
                state =  value[i]
        elif state == ST_0:
            if stable[i] and value[i]:
                state = ST_1
                edges.append(delay[i])
        elif state == ST_1:
            if stable[i] and not value[i]:
                state = ST_0
                edges.append(delay[i])
    
    return edges



    
def write_delay(carrier, value ):
#     not very relevant to delay Line
#     register used for configuring signal stability threshold for phase comparator 
    maxval = DDS_DELAY_MAXVAL_R( carrier.readl( DDS_REG_DELAY ) );
    
    carrier.writel(DDS_REG_DELAY, DDS_DELAY_VALUE_W( value ) | DDS_DELAY_ENABLE() | DDS_DELAY_LOAD() | DDS_DELAY_MAXVAL_W(maxval) );
    carrier.writel(DDS_REG_DELAY, DDS_DELAY_VALUE_W( value ) | DDS_DELAY_ENABLE() | DDS_DELAY_MAXVAL_W(maxval));
    carrier.writel(DDS_REG_DELAY, DDS_DELAY_VALUE_W( value ) | DDS_DELAY_ENABLE() | DDS_DELAY_LOAD() | DDS_DELAY_MAXVAL_W(maxval));

def main (card=None, default_directory='.',suite=None, configure_fpga=True):
    
    specid = spec_identification()[0];
    if specid < 0 :
        raise PtsError("Bus number of FmcDac600m12b1chadds was not found")
    
    carrier=spec( FIRMWARE_PATH, specid, configure_fpga );
    sys_pll = IC9(carrier);
    
    # default configuration
    vcxo_pll = IC26(carrier);
        
    print "*\n*\n*"
    print "DS18B20 on the FMC DDS test"
    print "*\n*\n*"

    carrier.temp.read_serial()
    if carrier.temp.serial[0] != 0x28:
        raise PtsError("DS18B20: serial number doesn't match specification!")
    else:
        print "Verified DS18B20 serial number!"

    print "Reading temperature from DS18B20 on the FMC DDS."
    temp = carrier.temp.read_temperature()
    if (temp<20) | (temp>80):
        print "Measured temperature might be incorrect!"


    print "*\n*\n*"
    print "Delay line test"
    print "*\n*\n*"
    
    test_frequency = 250e6

    print "Enabling clock generation with DAC"
    dacinst = dac(carrier);
    
    print "Configuring the IC26 (ad9516) to lock on DAC clock and output %f MHz to the FPGA" % (test_frequency/1e6)
    cwd=os.path.dirname(os.path.realpath(__file__))
    vcxo_pll.write_config(load_config(cwd+'/../doc/new_ad9516_pts_delayline.stp'))
    time.sleep(2)
    vcxo_pll.check_lock()


##########################################################    
##################### TEST ###############################
##########################################################
    
#     clock period in ns
    REF_PERIOD = 1e9/test_frequency
#     measurement tolerance in ns
    TOLERANCE = 0.5 
    delay_resolution = 0.010
    
#     configure number of same value consecutive samples
#     that are considered as stable signal  
    phase_comp_maxval(carrier, 1<<15 )
    
#     this test require iterating over all delay values with step delay_step
    delay_step = 16;
#     after delay configuration wait for stable_time before checking if sampled clock is stable
    stable_time = 0.3;
    
#     is sampled clock value stable
    stable=[];
#     last value
    value=[]
#     current delay set on delay line
    delay=[]
    
    print "The test clock outputted on the PULSE OUT connector"
    print "The PULSE OUT is connected with a cable with TRIG connector"
    print "The TRIG signal is connected to the FPGA"
    print "The TRIG signal is sampled with test clock"
    print "Depending on the loop latency the FPGA will sample constant value (or glitches)"
    print "The loop latency is controlled by the delay introduced by the PULSE OUT delay line"
    print "Based on the additional delay introduced by the programmable delay the test clock frequency is estimated and compared with the real one"

#     iterate over all possible delay values
    d = 0
    while ( d < 1024) :
        
#         set delay value
        write_delay(carrier, d);
        
#         wait for delay line and pll to stabilize
#         additionally stable_time * 8ns is required to determine stability
        time.sleep(stable_time)
        
        
#         read stability and last value
        tmp = carrier.readl(DDS_REG_DELPHASE);
        
#         save current delay
        delay.append(d)

#         save sampled value
        if tmp & DDS_DELPHASE_VALUE():
            value.append(1)
        else:
            value.append(0)
            
#         save information about stability of saved value
        if tmp & DDS_DELPHASE_STABLE():
            stable.append(1)
        else:
            stable.append(0)
        
#         might be useful for debugging
        print "delay: %f [ns], stable:%d, value: %d" % (delay_resolution*delay[len(delay)-1], stable[len(stable)-1], value[len(value)-1] )    
        
#         increase delay value
        d += delay_step;
        
                     
    edges = find_edges(value, stable, delay)
    
    if len(edges)<2:
        raise Exception("Didn't find 2 required to calculate clock period!")

    tmp_periods =[]
    
    edges1=edges[::2]
    if len(edges1)>=2:
        tmp_periods.append((edges1[-1]-edges1[0])/(len(edges1)-1))
    
    edges2=edges[1::2]
    if len(edges2)>=2:
        tmp_periods.append((edges2[-1]-edges2[0])/(len(edges2)-1))

    period = numpy.mean(tmp_periods)
    period *= delay_resolution
    
    vcxo_pll.test_config()

    print "Found following edges: "+str( [e*delay_resolution for e in edges])+ "[ns]"
    print "Calculated clock period: %f. Expected: %f +- %f]." %(period, REF_PERIOD, TOLERANCE)
    if abs( period - REF_PERIOD ) > TOLERANCE :
        raise PtsError("Delay line test failed")
    else:
        print "Delay line test succeeded"
    
    return 0
        
if __name__ == '__main__' :
    
    import argparse

    parser = argparse.ArgumentParser(description='Start test03')
    parser.add_argument('-p', action='store_true')
    args = vars(parser.parse_args())

    if args['p']:
        main(configure_fpga=False)
    else:
        main()