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

import calibr_box
import find_usb_tty


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

import calibration_box_connections
from Canvas import Line


import calibr_box
import find_usb_tty
import calibration_box_connections

from spec_identification import *


"""
test05: 
    Delay line test
        125 Mhz (8ns) 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 have the same frequency, 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
        Evaluated value will be significantly smaller then real value, since jitter will be added to measured period

        
"""



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

AUTOMATION = True






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

    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_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 ):
        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 found according to value change
#     on consecutive stable samples
#     in such a case edge time is calculated as middle of unstable area
    edgev=[]
    edges=[]

#     in order to calculate unstable edge time i need to know unstable area start and stop    
#     this flag is used to 
#     eliminate possibility that i detect only last unstable sample
    start = 1
    
#     print value
#     print stable
#     print delay

#     iterate over all pairs of samples
    for i in range(len(delay)-1):
#         unstable area border
        if (stable[i+1] != stable[i] ):
#             first edge may not be rising unstable edge
            if start == 1 :
                if stable[i+1] :
                    continue

#             store delay value for stable sample
            if stable[i+1] :
                edges.append(delay[i+1])
            else:
                edges.append(delay[i])
            
#             only used for first edge
            start=0
        
#         it is possible to get consecutive stable samples of different value
#         in that case there is no need to find canter of unstable area
        elif( value[i+1] != value[i] ) and ( stable[i+1]) and ( stable[i]) :
            edgev.append(delay[i+1])
            start=0

#     print edges
#     print edgev

#     this loop is used to calculate middle delay values of unstable area
#     iterating by 2 values because
#     first and last value is required
#     calculated edges are added to edgesv list
    i=2
    while( i <= len( edges ) ):
        edgev.append( ((edges[i-1] - edges[i-2])/2.0)+edges[i-2])
        i += 2
    
    
#     edges must be sorted 
#     print edgev
    edgev.sort()
    
    print "Detected edges:"
    print edgev

    return edgev



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

    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 PtsError( "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 PtsError("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);
    
    
    # default configuration after reset
#     vcxo_pll = ad9510(carrier, 1);
    
#     configure vcxo_pll to output 125 MHz to FPGA based on OSC 500 MHz
    vcxo_pll.write_reg(
        0x0A,
        ( B_BYPASS(0) | PRESCALLER(0) | POWER_DOWN(1) )
        );
    
#     divide freq by (1+1)+(1+1)
    vcxo_pll.write_reg( 0x54, 1 | 1<<4 );
            
    # divider bypassed
    vcxo_pll.write_reg(
        0x55,
        ( BYPASS(0) | NO_SYNC(0) | FORCE(0) | START_HL(0) | PHASE_OFFSET(0) )
        );
    
#     select clock source (osc)
    vcxo_pll.write_reg(
        0x45,
        ( SEL_CLK_IN(0) | CLK1_PD(0) | CLK2_PD( 0 ) |
            CLK_PLL_PD( 0 ) | REFIN_PD(0) | CLKS_IN_PD(0) )
        );
        
    # update configuration    
    vcxo_pll.write_reg(
        0x5A,
        UPDATE_REGISTERS(1)
        );
    
    time.sleep(5)
    

#     # check REFIN lineDDS_DELAY_VALUE_W( 0 ) | DDS_DELAY_ENABLE()
#     # first I have to make sure DAC is enabled    
#     dacinst = dac(carrier);
#     time.sleep(2)
    

##########################################################    
##################### TEST ###############################
##########################################################
    
#     clock period in ns
    REF_PERIOD = 8.0
#     measurement tolerance in ns
    TOLERANCE = 2 
    
#     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 = 1;
    
#     is sampled clock value stable
    stable=[];
#     last value
    value=[]
#     current delay set on delay line
    delay=[]
    
#     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: %d, stable:%d, value: %d" % (delay[len(delay)-1], stable[len(stable)-1], value[len(value)-1] )    
        
#         increase delay value
        d += delay_step;
        
                     
    edges = find_edges(value, stable, delay)
    
    #     calculate clock period based on sampled edges
    #     2 edges are required to measure half of period
    if len(edges) < 2:
        PtsError( "Not enough edges to calculate half period")
        return
    elif len( edges ) == 2:
        period = 2 * ( edges[1] - edges[0] )
        
        if abs( period - (REF_PERIOD*100.0) ) > 2*TOLERANCE*100 :
            PtsError("Delay line test failed")
        else:
            print "Delay line test succeeded"

    else:
        period = edges[2] - edges[0]

        if abs( period - (REF_PERIOD*100.0) ) > TOLERANCE*100 :
            PtsError("Delay line test failed")
        else:
            print "Delay line test succeeded"

   
    print "Clock period measured based on delay line phase detection is %f ns" % (period/100)

    

    
    return 0
        
if __name__ == '__main__' :
    main()

