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

# Copyright CERN, 2011 - 2013 (INCAA Computers BV)
# Author: Samuel Iglesias Gonsalvez <siglesia@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_si570.bin
#
# Description
#  Testing of the "Si570B, programmable oscillator" on the SPEC board (OSC4). The
#  firmware loaded to the FPGA implements the interface with
#  the oscillator, used for the oscillator's programming.
#  The I2C address of the Si570 is x55, predefined in the part number.
#
#  The FPGA is also receiving the 100MHz clock signal generated by the oscillator;
#  through a counter it counts clock cycles. Finally the output enable of 
#  the oscillator, OE_SI570, is controlled through the FPGA. 
#
#  The test starts by programming the Si570 oscillator through the I2C.
#  Then the output of the oscillator is enabled and we leave the dedicated counter
#  to count cycles for 2sec. Around 200M cycles are expected to be counted after 2sec
#  Finally, the output of the oscillator is disabled and the amount of counted cycles
#  is expected to be close to zero (note that residual noise may give few cycles).

import sys
import rr
import time
import os

from i2c import COpenCoresI2C
from si570 import CSI570

from ptsexcept import *

"""
test06: checks Silabs SI570 oscillator.
"""

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

def main (default_directory='.'):
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader';
    path_firmware = '../firmwares/spec_pts_si570.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);

    SI570_ADDR                  = 0x55
    SI570_I2C_WB_ADDR           = 0x40000
    SI570_OE_WB_ADDR            = 0x80008
    SI570_COUNTER_TOP_WB_ADDR   = 0x8000C
    SI570_COUNTER_VALUE_WB_ADDR = 0x80010
    SI570_COUNTER_RST_WB_ADDR   = 0x80014
    SI570_COUNT_EN_WB_ADDR      = 0x80018

    COUNTER_MARGIN = 10000000  # Expected value of a 100MHz counter after 100msec
    COUNTER_2SEC   = 200000000   # Expected value of a 100MHz counter after 2sec

    SI570_XTAL_FREQ_MIN = 114.056
    SI570_XTAL_FREQ_MAX = 114.514
    SI570_FOUT          = 100.000
    SI570_FOUT_TOL      = 1.0
    SI570_RFREQ         = 42
    SI570_N1            = 7
    SI570_HS_DIV        = 2

    # Load board library and open the corresponding device
    print "Loading hardware access library and opening devices\n"
    spec = rr.Gennum();
    i2c = COpenCoresI2C(spec, SI570_I2C_WB_ADDR, 99); # prescaler value (99), calculated for 50MHz clk
    si570 = CSI570(i2c, 0x55);

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

    # Reading of Si570 registers

    print "Reading the Si570 registers through I2C:\n"
    rfreq = si570.read_setup_oscillator()
    print "HS_DIV: " + str(si570.hs_div) + ", "
    print "N1: " + str(si570.n1) + ", "
    print "RFREQ: " + str(rfreq)  + "\n"
    time.sleep(1)

    # Checking the received values

    if (si570.hs_div == 0) or (si570.hs_div == 0x100) or (si570.hs_div == 0x110):
        msg = "ERROR: SI570: Invalid HS_DIV value: 0x%X\n" % (si570.hs_div)
        print msg
        ret_error = ret_error + "\n" + msg
    else:
        print "SI570: HS_DIV value: 0x%X OK\n" % si570.hs_div

    f_out_min = (SI570_XTAL_FREQ_MIN * rfreq) / (float(si570.n1) * float(si570.hs_div))
    f_out_max = (SI570_XTAL_FREQ_MAX * rfreq) / (float(si570.n1) * float(si570.hs_div))

    f_min_lim = SI570_FOUT - SI570_FOUT_TOL
    f_max_lim = SI570_FOUT + SI570_FOUT_TOL

    if (f_out_min < f_min_lim) or (f_out_min > SI570_FOUT):
        msg = "ERROR: SI570: Min frequency: %3.3f out of range[%3.3f .. %3.3f]" % (f_out_min, f_min_lim, SI570_FOUT)
        print msg
        ret_error = ret_error + "\n" + msg
    else:
        msg = "SI570: Min frequency: %3.3f OK" % (f_out_min)
        print "%s\n" % (msg)

    if (f_out_max > f_max_lim) or (f_out_max < SI570_FOUT):
        msg = "ERROR: SI570: Max frequency: %3.3f out of range[%3.3f .. %3.3f]" % (f_out_max, f_max_lim, SI570_FOUT)
        print msg
        ret_error = ret_error + "\n" + msg
    else:
        msg = "SI570: Max frequency: %3.3f OK\n" % (f_out_max)
        print "%s\n" % (msg)

    # Test with Si570 output enabled

    spec.iwrite(0,SI570_OE_WB_ADDR,4,1)              # Enable oscillator output
    spec.iwrite(0,SI570_COUNTER_TOP_WB_ADDR,4,0xFFFFFFFF) # Max value of the counter
    spec.iwrite(0,SI570_COUNT_EN_WB_ADDR,4,1)       # Enable the counter
    spec.iwrite(0,SI570_COUNTER_RST_WB_ADDR,4,1)     # Reset the counter
    spec.iwrite(0,SI570_COUNTER_RST_WB_ADDR,4,0)     # Remove counter reset
    time.sleep(2)                                 # Count during 2sec
    counter_value = spec.iread(0,SI570_COUNTER_VALUE_WB_ADDR,4)# Get counter value

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


    # Test with Si570 output disabled

    time.sleep(2)
    spec.iwrite(0,SI570_COUNTER_RST_WB_ADDR,4,1)     # Reset the counter
    time.sleep(2)

    spec.iwrite(0,SI570_OE_WB_ADDR,4,0)              # Disable oscillator output
    time.sleep(2)

    spec.iwrite(0,SI570_COUNTER_RST_WB_ADDR,4,0)     # Remove counter reset
    counter_value = spec.iread(0,SI570_COUNTER_VALUE_WB_ADDR,4)# Get counter value
    print ("Counter value: %d")%counter_value

    time.sleep(2)                                    # Count during 2sec
    counter_value = spec.iread(0,SI570_COUNTER_VALUE_WB_ADDR,4)# Get counter value

    if counter_value >= (COUNTER_2SEC):
        msg = "ERROR: SI570: Output disable failed. Counter value after 2sec: %d" % counter_value
        print msg
        ret_error = ret_error + "\n" + msg
    else:
        msg = "Si570: Disable OK; Counter value after 2sec is: %d" % counter_value
        print "%s\n" % (msg)

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


if __name__ == '__main__' :
    main();
