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

# Copyright CERN, 2012
# Author: Matthieu Cattin (CERN)
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 8/5/2012

# Import standard modules
import sys
import time
import random
import math

# Import specific modules
#import rr
from csr import *
from onewire import *
from gn4124 import *
from ds18b20 import *

# Import register maps
from spec_carrier_csr import *
from spec_irq_controller_regs import *
from utc_core_regs import *


# Class to access fmcadc100m14b4cha SPEC specific Wishbone cores

class FmcAdc100mSpecOperationError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return ("FmcAdc100mSpec: %s" %(self.msg))

class CFmcAdc100mSpec:

    # Wishbone core base addresses
    GNUM_DMA_CSR_ADDR = 0x1000
    ONEWIRE_ADDR = 0x1100
    CSR_ADDR = 0x1200
    UTC_CORE_ADDR = 0x2000
    IRQ_CONTROLLER_ADDR = 0x1300

    # Onewire core port
    DS18B20_PORT = 0

    # UTC core
    # tag = [wb_addr_offset, meta, second, coarse, fine]
    tag_trig = [0x8, 0, 0, 0, 0]
    tag_start = [0x18, 0, 0, 0, 0]
    tag_stop = [0x28, 0, 0, 0, 0]
    tag_end = [0x38, 0, 0, 0, 0]

    # IRQ controller
    IRQ_SRC_DMA_END = 0x1
    IRQ_SRC_DMA_ERR = 0x2
    IRQ_SRC_ACQ_TRG = 0x4
    IRQ_SRC_ACQ_END = 0x8

    # Gennum interface
    DMA_LENGTH = 4096 # DMA length in bytes


    #======================================================================
    # Class initialisation

    def __init__(self, bus, bs_type):
        self.bus = bus

        # Objects declaration
        self.dma_csr = CCSR(self.bus, self.GNUM_DMA_CSR_ADDR)
        self.gnum = CGN4124(self.bus, self.dma_csr)
        self.onewire = COpenCoresOneWire(self.bus, self.ONEWIRE_ADDR, 624, 124)
        self.ds18b20 = CDS18B20(self.onewire, self.DS18B20_PORT)
        self.csr = CCSR(self.bus, self.CSR_ADDR, CARRIER_CSR)
        self.utc_core = CCSR(self.bus, self.UTC_CORE_ADDR, UTC_CORE_REGS)
        self.irq_controller = CCSR(self.bus, self.IRQ_CONTROLLER_ADDR, IRQ_CONTROLLER_REGS)

        # Get physical addresses of the pages for DMA transfer
        self.dma_pages = self.gnum.get_physical_addr()

        # Check if bitstream is properly loaded by reading a register
        ct = self.get_carrier_type()
        if(ct == 0xFFFFFFFF):
            raise FmcAdc100mSpecOperationError("Bitstream not properly loaded.")

        # TODO
        # Check if the expected bitstream loaded
        #bs = self.get_bitstream_type()
        #if(bs != bs_type):
        #    raise FmcAdc100mSpecOperationError("Wrong bitsream. Excpect:0x%08X, Read:0x%08X" % (bs_type, bs))

        # Ckeck if a mezzanine is present
        if(not self.get_fmc_presence()):
            raise FmcAdc100mSpecOperationError("Mezzanine not present or PRSNT_M2C_L line faulty.")


    #======================================================================
    # Control and status registers (CSR)

    # Print carrier CSR register map
    def print_csr(self):
        self.csr.print_reg_map()

    # Get carrier type
    def get_carrier_type(self):
        try:
            return self.csr.get_field('CARRIER', 'TYPE')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get PCB revision
    def get_pcb_rev(self):
        try:
            return self.csr.get_field('CARRIER', 'PCB_REV')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get status register value
    def get_status(self):
        try:
            return self.csr.get_reg('STAT')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get control register value
    def get_control(self):
        try:
            return self.csr.get_reg('CTRL')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get FMC presence state
    # 1 = mezzanine present, 0 = no nezzanine
    def get_fmc_presence(self):
        try:
            return not self.csr.get_field('STAT', 'FMC_PRES')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get Gennum core P2L PLL lock state
    def get_p2l_pll_lock(self):
        try:
            return self.csr.get_field('STAT', 'P2L_PLL_LCK')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get system clock PLL lock state
    def get_sys_pll_lock(self):
        try:
            return self.csr.get_field('STAT', 'SYS_PLL_LCK')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get DDR3 controller calibration done state
    def get_ddr3_cal_done(self):
        try:
            return self.csr.get_field('STAT', 'DDR3_CAL_DONE')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set front panel green LED ON(1) or OFF(0)
    def set_green_led(self, value):
        try:
            return self.csr.set_field('CTRL', 'LED_GREEN', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set front panel red LED ON(1) or OFF(0)
    def set_red_led(self, value):
        try:
            return self.csr.set_field('CTRL', 'LED_RED', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)


    #======================================================================
    # Onewire thermometer and unique ID

    # print SPEC unique ID
    def print_unique_id(self):
        try:
            print('SPEC unique ID: %.12X') % self.ds18b20.read_serial_number()
        except DS18B20OperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # print SPEC temperature
    def print_temp(self):
        try:
            serial_number = self.ds18b20.read_serial_number()
            print("SPEC temperature: %3.3f°C") % self.ds18b20.read_temp(serial_number)
        except DS18B20OperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns SPEC unique ID
    def get_unique_id(self):
        try:
            return self.ds18b20.read_serial_number()
        except DS18B20OperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns SPEC temperature
    def get_temp(self):
        try:
            serial_number = self.ds18b20.read_serial_number()
            return self.ds18b20.read_temp(serial_number)
        except DS18B20OperationError as e:
            raise FmcAdc100mSpecOperationError(e)


    #======================================================================
    # UTC core

    # Print UTC core register map
    def print_utc_core_regs(self):
        self.utc_core.print_reg_map()

    # Set UTC core with current computer time
    def set_utc_time(self):
        try:
            current_time = time.time()
            utc_seconds = int(current_time)
            self.utc_core.set_reg('SECONDS', utc_seconds)
            utc_coarse = int((current_time - utc_seconds)/8E-9)
            self.utc_core.set_reg('COARSE', utc_coarse)
            return current_time
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns UTC seconds counter value
    def get_utc_second_cnt(self):
        try:
            return self.utc_core.get_reg('SECONDS')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set UTC seconds counter
    def set_utc_second_cnt(self, value):
        try:
            self.utc_core.set_reg('SECONDS', value)
            return self.utc_core.get_reg('SECONDS')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns UTC coarse counter value
    def get_utc_coarse_cnt(self):
        try:
            return self.utc_core.get_reg('COARSE')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set UTC coarse counter
    def set_utc_coarse_cnt(self, value):
        try:
            self.utc_core.set_reg('COARSE', value)
            return self.utc_core.get_reg('COARSE')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)


    ###########################################################################
    ##########                  Code to review                       ##########

    # Returns last trigger event time-tag
    def get_utc_trig_tag(self):
        # Get metadata
        addr = self.tag_trig[0]
        self.tag_trig[1] = self.utc_core.rd_reg(addr)
        # Get seconds
        addr = self.tag_trig[0]+0x4
        self.tag_trig[2] = self.utc_core.rd_reg(addr)
        # Get coarse
        addr = self.tag_trig[0]+0x8
        self.tag_trig[3] = self.utc_core.rd_reg(addr)
        # Get fine
        addr = self.tag_trig[0]+0xc
        self.tag_trig[4] = self.utc_core.rd_reg(addr)

        return self.tag_trig

    # Returns last acquisition start event time-tag
    def get_utc_start_tag(self):
        # Get metadata
        addr = self.tag_start[0]
        self.tag_start[1] = self.utc_core.rd_reg(addr)
        # Get seconds
        addr = self.tag_start[0]+0x4
        self.tag_start[2] = self.utc_core.rd_reg(addr)
        # Get coarse
        addr = self.tag_start[0]+0x8
        self.tag_start[3] = self.utc_core.rd_reg(addr)
        # Get fine
        addr = self.tag_start[0]+0xc
        self.tag_start[4] = self.utc_core.rd_reg(addr)

        return self.tag_start

    # Returns last acquisition stop event time-tag
    def get_utc_stop_tag(self):
        # Get metadata
        addr = self.tag_stop[0]
        self.tag_stop[1] = self.utc_core.rd_reg(addr)
        # Get seconds
        addr = self.tag_stop[0]+0x4
        self.tag_stop[2] = self.utc_core.rd_reg(addr)
        # Get coarse
        addr = self.tag_stop[0]+0x8
        self.tag_stop[3] = self.utc_core.rd_reg(addr)
        # Get fine
        addr = self.tag_stop[0]+0xc
        self.tag_stop[4] = self.utc_core.rd_reg(addr)

        return self.tag_stop

    # Returns last acquisition end event time-tag
    def get_utc_end_tag(self):
        # Get metadata
        addr = self.tag_end[0]
        self.tag_end[1] = self.utc_core.rd_reg(addr)
        # Get seconds
        addr = self.tag_end[0]+0x4
        self.tag_end[2] = self.utc_core.rd_reg(addr)
        # Get coarse
        addr = self.tag_end[0]+0x8
        self.tag_end[3] = self.utc_core.rd_reg(addr)
        # Get fine
        addr = self.tag_end[0]+0xc
        self.tag_end[4] = self.utc_core.rd_reg(addr)

        return self.tag_end

    ###########################################################################


    #======================================================================
    # Interrupt controller core

    # Print IRq controller register map
    def print_irq_controller_regs(self):
        self.irq_controller.print_reg_map()

    # Set IRQ enable mask
    def set_irq_en_mask(self, mask):
        try:
            self.irq_controller.set_reg('EN_MASK', mask)
            return self.irq_controller.get_reg('EN_MASK')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get IRQ enable mask
    def get_irq_en_mask(self):
        try:
            return self.irq_controller.get_reg('EN_MASK')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns multiple IRQ status
    def get_irq_mult(self):
        try:
            return self.irq_controller.get_reg('MULTI_IRQ')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Clears multiple IRQ status
    def clear_irq_mult(self, irq):
        try:
            self.irq_controller.set_reg('MULTI_IRQ', irq)
            return self.irq_controller.get_reg('MULTI_IRQ')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Returns IRQ source
    def get_irq_source(self):
        try:
            return self.irq_controller.get_reg('SRC')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Clears IRQ source
    def clear_irq_source(self, irq):
        try:
            self.irq_controller.set_reg('SRC', irq)
            return self.irq_controller.get_reg('SRC')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set dma done interrupt mask
    def set_irq_dma_done_mask(self, value):
        try:
            return self.irq_controller.set_field('EN_MASK', 'DMA_END', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set acquisition end interrupt mask
    def set_irq_acq_end_mask(self, value):
        try:
            return self.irq_controller.set_field('EN_MASK', 'ACQ_END', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set trigger interrupt mask
    def set_irq_trig_mask(self, value):
        try:
            return self.irq_controller.set_field('EN_MASK', 'ACQ_TRG', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)


    #======================================================================
    # Data acquisition (DMA transfer)


    ###########################################################################
    ##########                  Code to review                       ##########

    # Make a DMA transfer
    #   carrier_addr and length are in bytes
    def get_data(self, carrier_addr, length):
        # Configure DMA
        if length == 0:
            raise Exception('Cannot retreive data of zero length!')
        items_required = int(math.ceil(length/float(self.DMA_LENGTH)))
        if(128 < items_required):
            print('Required items: %d')%items_required
            raise Exception('Current gn4124 class only supports up to 128 items.')
        #print('Required items: %d')%items_required
        for num in range(items_required):
            if(items_required == num+1):
                next_item = 0
                item_length = (length-(num*self.DMA_LENGTH))
            else:
                next_item = 1
                item_length = self.DMA_LENGTH
            if(0 == num):
                item_start_addr = carrier_addr
            else:
                item_start_addr = carrier_addr + (num*self.DMA_LENGTH)
            #print("item nb:%d item_carrier_addr:0x%.8X item_host_addr:0x%.8X item_length:%d next_item:%d)")%(num,item_start_addr,self.dma_pages[num+1],item_length,next_item)
            self.gnum.add_dma_item(item_start_addr, self.dma_pages[num+1], item_length, 0, next_item)
        items = self.gnum.get_memory_page(0)
        #self.gnum.print_dma_items()
        #print('DMA items:')
        #for i in range(items_required*8):
        #    print('%.4X: %.8X')%(i*4,items[i])
        #self.print_irq_controller_regs()
        #time.sleep(3)
        # Start DMA
        self.gnum.start_dma()
        # Wait for end of DMA interrupt
        #print('Wait GN4124 interrupt')
        self.gnum.wait_irq()
        #print('GN4124 interrupt occured')
        dma_finished = 0
        #self.print_irq_controller_regs()
        #print('irq mask:%.4X')%self.get_irq_en_mask()
        while(0 == dma_finished):
            irq_src = self.get_irq_source()
            #print('IRQ source : %.4X')%irq_src
            #print('DMA status: %s')%self.gnum.get_dma_status()
            if(irq_src & self.IRQ_SRC_DMA_END):
                #print('IRQ source : %.4X')%irq_src
                self.clear_irq_source(self.IRQ_SRC_DMA_END)
                #print('IRQ source : %.4X')%self.get_irq_source()
                dma_finished = 1
            time.sleep(0.005)
        #print('DMA finished!')
        # Retrieve data from host memory
        data = []
        for i in range(items_required):
            data += self.gnum.get_memory_page(i+1)
        #print('data length:%d')%(len(data)*4)
        channels_data = []
        for i in range(length/4):
            channels_data.append(data[i] & 0xFFFF)
            channels_data.append(data[i]>>16)
        #print('channels data length:%d')%(len(channels_data))
        return channels_data

    # Wait end of acquisition
    def wait_end_acq(self):
        acq_end = 0
        while(0 == acq_end):
            irq_src = self.get_irq_source()
            print('IRQ source : %.4X')%irq_src
            if(irq_src & self.IRQ_SRC_ACQ_END):
                self.clear_irq_source(self.IRQ_SRC_ACQ_END)
                print('IRQ source : %.4X')%self.get_irq_source()
                acq_end = 1
        print('ACQ finished!')


    # Writes data pattern to carrier's DDR memory
    def put_data(self, carrier_addr, pattern, length, verbose=False):
        # Configure DMA
        items_required = int(math.ceil(length/float(self.DMA_LENGTH)))
        if(128 < items_required):
            print('Required items: %d')%items_required
            raise Exception('Current gn4124 class only supports up to 128 items.')
        if verbose:
            print('Required items: %d')%items_required
        for num in range(items_required):
            if(items_required == num+1):
                next_item = 0
                item_length = (length-(num*self.DMA_LENGTH))
            else:
                next_item = 1
                item_length = self.DMA_LENGTH
            if(0 == num):
                item_start_addr = carrier_addr
            else:
                item_start_addr = carrier_addr + (num*self.DMA_LENGTH)
            #print("item nb:%d item_carrier_addr:0x%.8X item_host_addr:0x%.8X item_length:%d next_item:%d)")%(num,item_start_addr,self.dma_pages[num+1],item_length,next_item)
            self.gnum.add_dma_item(item_start_addr, self.dma_pages[num+1], item_length, 1, next_item)
        items = self.gnum.get_memory_page(0)
        #self.gnum.print_dma_items()
        #print('DMA items:')
        #for i in range(items_required*8):
        #    print('%.4X: %.8X')%(i*4,items[i])

        # Write pattern to DMA reserved host memory pages
        for page in range(1,items_required):
            if verbose:
                print("Writting %8X pattern on host memory page %d"%(pattern, page))
            self.gnum.set_memory_page(page, pattern)

        #self.print_irq_controller_regs()
        #time.sleep(3)
        # Start DMA
        self.gnum.start_dma()
        # Wait for end of DMA interrupt
        if verbose:
            print('Wait GN4124 interrupt')
        self.gnum.wait_irq()
        if verbose:
            print('GN4124 interrupt occured')
        dma_finished = 0
        if verbose:
            #self.print_irq_controller_regs()
            print('irq mask:%.4X')%self.get_irq_en_mask()
        while(0 == dma_finished):
            irq_src = self.get_irq_source()
            #print('IRQ source : %.4X')%irq_src
            #print('DMA status: %s')%self.gnum.get_dma_status()
            if(irq_src & self.IRQ_SRC_DMA_END):
                #print('IRQ source : %.4X')%irq_src
                self.clear_irq_source(self.IRQ_SRC_DMA_END)
                #print('IRQ source : %.4X')%self.get_irq_source()
                dma_finished = 1
            time.sleep(0.005)
        if verbose:
            print('DMA finished!')

    # Writes pattern to entire carrier's DDR memory
    def clear_ddr(self, carrier_addr, pattern, length, verbose=False):
        # Configure DMA
        # Use page 0 to 254 to store dma items
        # Use page 255 to store pattern
        items_required = int(math.ceil(length/float(self.DMA_LENGTH)))
        if(128*255 < items_required):
            print('Required items: %d')%items_required
            raise Exception('Current gn4124 class only supports up to %d items.'%(128*255))
        if verbose:
            print('Required items: %d')%items_required
        for num in range(items_required):
            if(items_required == num+1):
                next_item = 0
                item_length = (length-(num*self.DMA_LENGTH))
            else:
                next_item = 1
                item_length = self.DMA_LENGTH
            if(0 == num):
                item_start_addr = carrier_addr
            else:
                item_start_addr = carrier_addr + (num*self.DMA_LENGTH)
            #print("item nb:%d item_carrier_addr:0x%.8X item_host_addr:0x%.8X item_length:%d next_item:%d)")%(num,item_start_addr,self.dma_pages[num+1],item_length,next_item)
            self.gnum.add_dma_item(item_start_addr, self.dma_pages[-1], item_length, 1, next_item)
        #self.gnum.print_dma_items()

        # Write pattern to DMA reserved host memory page (last page)
        self.gnum.set_memory_page(255, pattern)

        # Start DMA
        self.gnum.start_dma()
        # Wait for end of DMA interrupt
        if verbose:
            print('Wait GN4124 interrupt')
        self.gnum.wait_irq()
        if verbose:
            print('GN4124 interrupt occured')
        dma_finished = 0
        if verbose:
            #self.print_irq_controller_regs()
            print("irq source : %.8X"%self.get_irq_source())
            print('irq mask:%.4X')%self.get_irq_en_mask()

        while(0 == dma_finished):
            irq_src = self.get_irq_source()
            if verbose:
                print('irq source (polling loop): %.8X')%irq_src
            #print('DMA status: %s')%self.gnum.get_dma_status()
            if(irq_src & self.IRQ_SRC_DMA_END):
                #print('IRQ source : %.4X')%irq_src
                self.clear_irq_source(self.IRQ_SRC_DMA_END)
                if verbose:
                    print("irq source : %.8X"%self.get_irq_source())
                dma_finished = 1
            time.sleep(0.005)
        if verbose:
            print('DMA finished!')

    ###########################################################################
