#!/usr/bin/python
#coding: utf8

# Copyright CERN, 2011 - 2013 (INCAA Computers BV)
# Author: Carlos Gil Soriano <cgilsori@cern.ch>
# Author: Julian Lewis (Julian.Lewis@cern.ch)
# Author: INCAA Computers BV (info@incaacomputers.com)
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Website: http://www.incaacomputers.com
# Version: 2.0 (Last modifications: 13/06/2013)
#
# FPGA firmware: spec_pts_vcxo_pll.bin
#
# Description 
#  Testing of the DAC IC3, VCXO OSC2, DAC IC7, VCXO OSC5, PLL IC6 chips.
#  The DAC IC3 controls the operation of the 20MHz VCXO OSC2 oscillator, and from
#  this moment on we would refer to it as DAC_VCXO.
#  The DAC IC7 controls the 25MHz VCXO OSC5 which in turn controls the PLL IC6;
#  from this moment on we would refer to it as DAC_PLL.
#              __________        ________ 
#      -------|          |      |        |
#  SPI -----|-| DAC_VCXO |----->|  VCXO  |--------------------> 20MHz clk
#      ---|-|-|__________|      |________|
#       | | |  __________        ________        ________ 
#       | |  -|          |      |        |      |        |
#       |  ---| DAC_PLL  |----->|  OSC5  |----->|PLL IC6 |----> PLLREF 125 MHz clk
#        -----|__________|      |________|      |________|
#
#  The test starts by programming the DACs.
#  The VCXO and the PLL counters are enabled and count clock cycles for ~2secs; the
#  amount of counted cycles is an indication of the clock's frequency.
#  The test continues with reprogramming the DACs and confirming that the amount of
#  counted cycles by the counters changes accordingly; the higher the DAC value the
#  faster the oscillators become and the bigger the amount of cycles counted by the
#  counters.

import sys
import os
import rr
import time
import math

from spi import COpenCoresSPI

from ctypes import *
from ptsexcept import *        # jdgc

""" 
test08: checks the 20 MHz system clock and the output of the CDC5662 PLL
"""

##-------------------------------------------------------------------------------------------------
##                                       DAC AD5662 class                                        --
##-------------------------------------------------------------------------------------------------

class DAC_AD5662:

    PD0 = 16
    PD1 = 17

    OM_NORMAL   = 0
    OM_1K       = 1
    OM_100K     = 2
    OM_TRISTATE = 3

    V_REF = 3.3

    def __init__(self, spi, slave):
        self.dac_value = 0x0000
        self.op = self.OM_NORMAL
        self.spi = spi
        self.slave = slave

    def set_dac_voltage(self, voltage):
        if (voltage >= self.V_REF):
            "FAIL: please set a valid DAC_AD5662 voltage"
        value = int(voltage / self.V_REF * 65536)
        self.dac_value = value

    def operation_mode(self, op):
        self.op = op

    def update_output(self):
        #data = (self.op << 16) + (self.dac_value)
        data = []
        data.append((self.dac_value & 0x00FF))
        data.append((self.dac_value & 0xFF00) >> 8)
        data.append(self.op)
        self.spi.transaction(self.slave, data)     

##-------------------------------------------------------------------------------------------------
##                                             main                                              --
##-------------------------------------------------------------------------------------------------

def main (default_directory='.'):
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader';
    path_firmware = '../firmwares/spec_pts_vcxo_pll.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print "Loading firmware: %s" % (firmware_loader + ' ' + bitstream)
    os.system( firmware_loader + ' ' + bitstream)
    #time.sleep(2);

    # Load board library and open the corresponding device
    print "Loading hardware access library and opening devices\n"
    spec = rr.Gennum();

    print "Test08 Start"
    init_test_time = time.time()
    ret_error = None

    """
    tests the: DAC IC3, VCXO OSC2, DAC IC7, VCXO OSC5, PLL IC6
    """

    DAC_SPI_WB_ADDR = 0x40000
    DAC_PLL_SPI_SLAVE  = 0
    DAC_VCXO_SPI_SLAVE = 1

    VCXO_COUNTER_WB_BASE_ADDR = 0x60000
    PLL_COUNTER_WB_BASE_ADDR  = 0x80000

    COUNTER_TOP_WB_ADDR   = 0x00C
    COUNTER_RST_WB_ADDR   = 0x014
    COUNTER_ENABL_WB_ADDR = 0x018
    COUNTER_VALUE_WB_ADDR = 0x010

    VCXO_COUNTER_MARGIN = 2000000   # Expected value of a 20MHz counter after 100msec
    VCXO_COUNTER_2SEC   = 40000000  # Expected value of a 20MHz counter after 2sec
    PLL_COUNTER_MARGIN  = 12500000  # Expected value of a 125MHz counter after 100msec
    PLL_COUNTER_2SEC    = 250000000 # Expected value of a 125MHz counter after 2sec

    dac_volts = [0.1, 0.8, 1.65, 2.4, 3.2]
    vcxo = []
    pll = []


    for dac_volt in dac_volts:

        # Setting DAC_VCXO

        dac_vcxo_spi = COpenCoresSPI(spec, DAC_SPI_WB_ADDR, 0x004) # divider=0x004: SCLK would be 5 MHz
        dac = DAC_AD5662(dac_vcxo_spi, DAC_VCXO_SPI_SLAVE)
        dac.operation_mode(dac.OM_NORMAL)
        dac.set_dac_voltage(dac_volt)
        dac.update_output()

        # Setting DAC_PLL

        dac_pll_spi = COpenCoresSPI(spec, DAC_SPI_WB_ADDR, 0x004)   # divider=0x004: SCLK woulb be 5 MHz
        dac = DAC_AD5662(dac_pll_spi, DAC_PLL_SPI_SLAVE)
        dac.operation_mode(dac.OM_NORMAL)
        dac.set_dac_voltage(dac_volt)
        dac.update_output()

        # Init Top of VCXO & PLL Counters

        spec.iwrite(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_TOP_WB_ADDR, 4, 0xFFFFFFFF)
        spec.iwrite(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_TOP_WB_ADDR, 4, 0xFFFFFFFF)

        # Resetting VCXO & PLL Counters

        spec.iwrite(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_RST_WB_ADDR, 4, 1)              # Reset VCXO counter
        spec.iwrite(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_RST_WB_ADDR, 4, 1)               # Reset PLL IC6 counter
        vcxo_count = spec.iread(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_VALUE_WB_ADDR, 4)   # Read VCXO counter, counter is under reset
        pll_ic6_count = spec.iread(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_VALUE_WB_ADDR, 4) # Read PLL IC6 counter, counter is under reset

        # Enabling VCXO & PLL Counters

        spec.iwrite(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_ENABL_WB_ADDR, 4, 1)  # Enable VCXO counter
        spec.iwrite(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_ENABL_WB_ADDR, 4, 1)   # Enable PLL IC6 counter
        spec.iwrite(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_RST_WB_ADDR, 4, 0)    # Release VCXO counter reset
        spec.iwrite(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_RST_WB_ADDR, 4, 0)     # Release PLL IC6 counter reset

        # VCXO & PLL Counters counting for 2 secs..

        time.sleep(2)
        spec.iwrite(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_ENABL_WB_ADDR, 4, 0)  # Disable VCXO counter
        spec.iwrite(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_ENABL_WB_ADDR, 4, 0)   # Disable PLL IC6 counter

        # Reading VCXO & PLL Counters

        vcxo_count = spec.iread(0, VCXO_COUNTER_WB_BASE_ADDR + COUNTER_VALUE_WB_ADDR, 4) # Read VCXO counter
        pll_count = spec.iread(0, PLL_COUNTER_WB_BASE_ADDR + COUNTER_VALUE_WB_ADDR, 4)   # Read PLL IC6 counter
        vcxo.append(vcxo_count)
        pll.append(pll_count)

        # Evaluating the VCXO and PLL clock frequencies at DAC middle value 1.65

        if dac_volt == 1.65:
            if (vcxo_count > (VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN)) and  (vcxo_count < (VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)):
                msg = "VCXO OSC2: Frequency OK; Counter value after 2sec: %d is within range[%d .. %d]" % (vcxo_count, VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN, VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)
                print("%s\n" % (msg))
            else:
                msg = "ERROR: VCXO OSC2: Wrong frequency; Counter value after 2sec: %d out of range[%d .. %d]" % (vcxo_count, VCXO_COUNTER_2SEC-VCXO_COUNTER_MARGIN, VCXO_COUNTER_2SEC+VCXO_COUNTER_MARGIN)
                print(msg)
                ret_error = msg

            if (pll_count > (PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN)) and  (pll_count < (PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)):
                msg = "PLL IC6: Frequency OK; Counter value after 2sec: %d is within range[%d .. %d]"  % (pll_count, PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN, PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)
                print("%s\n" % (msg))
            else:
                msg = "ERROR: PLL IC6: Wrong frequency; Counter value after 2sec: %d out of range[%d .. %d]" % (pll_count, PLL_COUNTER_2SEC-PLL_COUNTER_MARGIN, PLL_COUNTER_2SEC+PLL_COUNTER_MARGIN)
                print(msg)
                ret_error = msg
            print("\n")

    # Evaluating the VCXO and PLL influence from DAC

    for i in range(len(dac_volts)):
        grad = vcxo[i] / dac_volts[i]
        if i > 0:
            diff = grad - ograd
            if diff < 0:
                print("VCXO OSC2: Frequency responds to change in DAC IC3 OK. DAC=%3.2fV\n" % (dac_volts[i]))
            else:
                msg = "ERROR: VCXO OSC2: Frequency not responding to change in DAC IC3 control value"
                print(msg)
                ret_error = msg
        ograd = grad

    print("\n")

    for i in range(len(dac_volts)):
        grad = pll[i] / dac_volts[i]
        if i > 0:
            diff = grad - ograd
            if diff < 0:
                print("PLL IC6: Frequency responds to change in DAC IC7 OK.  DAC=%3.2fV\n" % (dac_volts[i]))
            else:
                msg = "ERROR: PLL IC6: Frequency not responding to change in DAC IC7 control value"
                print(msg)
                ret_error = msg
        ograd = grad

    print("\n")

    end_test_time = time.time()
    print "End of Test08"
    print "RESULT: [{}]".format("FAIL" if ret_error else "OK")
    print 'Test08 elapsed time: {:.2f} seconds'.format(end_test_time-init_test_time)
    if ret_error:
        raise PtsError(ret_error)

if __name__ == '__main__':
    main()
