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

# Copyright CERN, 2012 - 2013 (Seven Solutions S.L., INCAA Computers BV)
# Author: Richard Carrillo <rcarrillo(AT)sevensols.com>
# Author: Rafael Rodríguez <rrodriguez(AT)sevensols.com>
# Author: INCAA Computers BV (info@incaacomputers.com)
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Website: http://www.sevensols.com
# Website: http://www.incaacomputers.com
# Version: 2.0 (Last modifications: 13/06/2013)
#
# FPGA firmware: spec_pts_ddr.bin
#
# Description 
#  Testing of the DDR3 memory IC4 chip.
#  The memory is tested by writing different data value to the memory at
#  different addresses so every data and address line is tested.

import sys
import rr
import time
import gn4124
import os
import math

from ptsexcept import *

"""
test07: Check the connectivity of data and address lines of DDR memory
Conditions: SPEC board correctly configured (test00 passed and jumper removed)
User intervention required: No
Procedure details:
- Load FPGA firmware
- Set local bus frequency
- Test the connectivity of data lines
- Test the connectivity of address lines
"""

class test07:
    GN4124_CSR = 0x0
    dma_length = 0x4 # DMA block length in bytes
    # parameters of the memory interface
    num_addr_lines = 14
    num_bank_lines = 3
    num_data_lines = 16
    def __init__(self, spec):
        """ Initialize (get a list of pages for DMA access, configure interrupts) """
        try:
            self.gennum = gn4124.CGN4124(spec, self.GN4124_CSR) # Initialize the board
            self.gennum.set_local_bus_freq(160) # Set local bus frequency
            self.pages = self.gennum.get_physical_addr() # Get host memory pages physical address
        except:
            exctype, value = sys.exc_info()[:2]
            err_msg="While board initialization: "+str(exctype)+" : "+str(value)
            raise PtsError(err_msg) # Non-critical error: further tests could be performed successfully
        if len(self.pages) < 1:
            raise PtsError("While board initialization: Not enough host memory pages for DMA access")

    def wishbone_address_bit_to_DDR_interface_line(self,wishbone_addr_bit_number):
        first_bank_bit_in_wishbone_addr=11
        first_row_bit_in_wishbone_addr=14
        first_column_bit_in_wishbone_addr=1
        if wishbone_addr_bit_number > 10 and wishbone_addr_bit_number < 14:
            DDR_interface_line="bank line {}".format(wishbone_addr_bit_number-first_bank_bit_in_wishbone_addr)
        elif wishbone_addr_bit_number >= first_row_bit_in_wishbone_addr:
            DDR_interface_line="address line {}".format(wishbone_addr_bit_number-first_row_bit_in_wishbone_addr)
        elif    wishbone_addr_bit_number >= first_column_bit_in_wishbone_addr:
            DDR_interface_line="address line {}".format(wishbone_addr_bit_number-first_column_bit_in_wishbone_addr)
        else:
            DDR_interface_line="line"
        return DDR_interface_line

    def test_address_and_bank_lines(self):
        first_test_wishbone_addr_bit=11
        memory_wishbone_addr_mask=0x0FFFFFFF
        test_wishbone_addr_mask=memory_wishbone_addr_mask ^ ((0x1 << first_test_wishbone_addr_bit)-1)
        """ Test the memory-interface address lines """
        # Bank lines are tested first and then the address lines (rows)
        ret_error=None
        addr_list=[(0x1 << addr_line-1+first_test_wishbone_addr_bit) & test_wishbone_addr_mask for addr_line in range(self.num_addr_lines+self.num_bank_lines+1)] # DDR addresses: 0x00000000, 0x00000800, 0x00001000, 0x00002000...
        value_list=[addr_line+1 for addr_line in range(self.num_addr_lines+self.num_bank_lines+1)] # values to write in DDR adresses: 1, 2, 3, 4...
        for addr_line in reversed(range(len(addr_list))): # Write value_list in DDR addr_list in reverse order
            print("Address line: [%d]" % (addr_line))
            self.gennum.set_memory_page(0, value_list[addr_line])
            self.gennum.add_dma_item(addr_list[addr_line], self.pages[0], self.dma_length, 1, 0) # write host page[0] in addr_list[addr_line]
            self.gennum.start_dma()
            self.gennum.wait_irq()
        for addr_line in reversed(range(len(addr_list))): # Compare values in DDR addr_list in reverse order
            self.gennum.add_dma_item(addr_list[addr_line], self.pages[0], self.dma_length, 0, 0) # Store addr_list[addr_line] in host page[0]
            self.gennum.start_dma()
            self.gennum.wait_irq()
            page_data = self.gennum.get_memory_page(0)
            if page_data[0] <> value_list[addr_line]:
                error_str="Value read from memory (0x{:X}) is differnt from what was expected (0x{:X}) at Wishbone address (0x{:X}). At least DDR-interface {} is tied to Vcc or GND, or unconnected.".format(page_data[0],value_list[addr_line],addr_list[addr_line],self.wishbone_address_bit_to_DDR_interface_line(0 if addr_line==0 else addr_line-1+first_test_wishbone_addr_bit))
                print(error_str)
                ret_error="While testing DDR address and bank lines: "+error_str
                break
        return ret_error

    def find_different_bit(self,dword0,dword1):
        """ Return the number of the most significant bit which is different in the two values """
        return int(math.floor(math.log(dword0^dword1,2)))

    def test_data_lines(self):
        """ Test the memory-interface data lines """
        ret_error=None
        test_data_addr=0x0 # any valid address is OK
        value_list=[0x0001 << data_line for data_line in range(self.num_data_lines)] # DDR data values: 0x0001, 0x0002, 0x0004, 0x0008...
        for data_line in range(len(value_list)): # Write value_list in DDR address 0
            print("Data line: [%d]" % (data_line))
            self.gennum.set_memory_page(0, value_list[data_line])
            self.gennum.add_dma_item(test_data_addr, self.pages[0], self.dma_length, 1, 0) # write host page[0] in DDR test_data_addr
            self.gennum.start_dma()
            self.gennum.wait_irq()
            self.gennum.add_dma_item(test_data_addr, self.pages[0], self.dma_length, 0, 0) # Store content of DDR test_data_addr in host page[0]
            self.gennum.start_dma()
            self.gennum.wait_irq()
            page_data = self.gennum.get_memory_page(0)
            if page_data[0] <> value_list[data_line]:
                error_str="Value read from memory (0x{:X}) is differnt from what was expected (0x{:X}) at Wishbone address (0x{:X}). At least DDR-interface data line {} is tied to Vcc or GND, or unconnected.".format(page_data[0],value_list[data_line],test_data_addr,self.find_different_bit(page_data[0],value_list[data_line]))
                print(error_str)
                ret_error="While testing DDR data lines: "+error_str
                break
        return ret_error


def main(default_directory="."):
    # Configure the FPGA using the program fpga_loader
    path_fpga_loader = '../firmwares/fpga_loader'
    path_firmware = '../firmwares/spec_pts_ddr.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 device\n"
    spec = rr.Gennum()

    print "Test07 Start"
    init_test_time = time.time()

    print "Configuring board"
    test=test07(spec)
    print "\nChecking DDR data lines"
    ret_error=test.test_data_lines()
    if ret_error==None:
        print "Checking DDR address and bank lines"
        ret_error=test.test_address_and_bank_lines()

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


if __name__ == "__main__":
    main(".")
