#!   /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 *
from vic import *

# Import register maps
from spec_carrier_csr import *
from dma_eic_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
    VIC_ADDR = 0x1300
    GNUM_DMA_EIC_ADDR = 0x1400

    # 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]

    # 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.vic = CVic(self.bus, self.VIC_ADDR)
        self.dma_eic = CCSR(self.bus, self.GNUM_DMA_EIC_ADDR, DMA_EIC_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.")

        # Release the mezzanine software reset
        self.set_sw_rst(1)

        # Configure VIC
        #self.vic.print_regs()
        self.vic.set_polarity(1) # output active high
        self.vic.enable_emu_edge(750) # emulate edge sensitive output (pulse width = 750 * 1/sys_clk = 6us)
        self.vic.enable_int(0) # enable interrupts from fmc0 eic
        self.vic.enable_int(1) # enable interrupts from dma eic
        self.vic.enable_module()
        self.fmc_eic_addr = self.vic.get_vector_addr(0)
        self.dma_eic_addr = self.vic.get_vector_addr(1)

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

    # Mezzanine software reset
    def sw_rst(self):
        try:
            self.csr.set_field('RST', 'FMC0', 0)
            time.sleep(0.001)
            self.csr.set_field('RST', 'FMC0', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Set mezzanine software reset state
    def set_sw_rst(self, value):
        try:
            return self.csr.set_field('RST', 'FMC0', value)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get mezzanine software reset state
    def get_sw_rst(self):
        try:
            return self.csr.get_field('RST', 'FMC0')
        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)


    #======================================================================
    # Vectored Interrupt Controller (VIC)

    # Get current interrupt vector address
    def vic_get_current_vector_addr(self):
        return self.vic.get_int_vector_addr()

    # Acknowledge current vector
    def vic_ack_current_vector(self):
        self.vic.int_ack()

    # Get raw interrupt status
    def vic_get_raw_status(self):
        return self.vic.get_raw_int_status()

    #======================================================================
    # DMA Enhanced Interrupt Controller (EIC)

    # Print DMA EIC register map
    def print_dma_eic_regs(self):
        self.dma_eic.print_reg_map()

    # Enable DMA done interrupt
    def enable_dma_done_irq(self):
        try:
            self.dma_eic.set_field('IER', 'DMA_END', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Disable DMA done interrupt
    def disable_dma_done_irq(self):
        try:
            self.dma_eic.set_field('IDR', 'DMA_END', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Clear DMA done interrupt
    def clear_dma_done_irq(self):
        try:
            self.dma_eic.set_field('ISR', 'DMA_END', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Enable DMA error interrupt
    def enable_dma_error_irq(self):
        try:
            self.dma_eic.set_field('IER', 'DMA_ERR', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Disable DMA error interrupt
    def disable_dma_error_irq(self):
        try:
            self.dma_eic.set_field('IDR', 'DMA_ERR', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Clear DMA error interrupt
    def clear_dma_error_irq(self):
        try:
            self.dma_eic.set_field('ISR', 'DMA_ERR', 1)
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get DMA EIC enable mask
    def get_dma_eic_en_mask(self):
        try:
            return self.dma_eic.get_reg('IMR')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get DMA EIC interrupt source
    def get_dma_eic_src(self):
        try:
            return self.dma_eic.get_reg('ISR')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get DMA done interrupt status
    def get_dma_done_status(self):
        try:
            return self.dma_eic.get_field('ISR', 'DMA_END')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)

    # Get DMA error interrupt status
    def get_dma_error_status(self):
        try:
            return self.dma_eic.get_field('ISR', 'DMA_ERR')
        except CSRDeviceOperationError as e:
            raise FmcAdc100mSpecOperationError(e)


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


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

    # Configure DMA transfer
    #   carrier_addr : dma start address on the carrier side (in bytes)
    #   length       : dma total length (in bytes)
    def cfg_dma(self, carrier_addr, length, verbose=False):
        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.')
        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)
            if verbose:
                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)
        return items_required
        #self.gnum.print_dma_items()
        #print('DMA items:')
        #for i in range(items_required*8):
        #    print('%.4X: %.8X')%(i*4,items[i])

    # Start DMA and wait for DMA DONE interrupt
    def make_dma(self, verbose=False):
        # Start DMA
        self.gnum.start_dma()
        # Wait for end of DMA interrupt
        if verbose:
            print("VIC enable mask: 0x%08X"%(self.vic.get_int_enable_mask()))
            print("DMA EIC enable mask: 0x%08X"%self.get_dma_eic_en_mask())
        self.gnum.wait_irq(verbose)
        # Check if interrupt is DMA DONE
        if verbose:
            print('Check if interrupt is DMA_DONE')
        irq_src = self.get_dma_eic_src()
        irq_vect = self.vic_get_current_vector_addr()
        if verbose:
            print("DMA EIC irq source : %.8X"%irq_src)
            print("VIC current vector : 0x%08X"%irq_vect)
        if(irq_vect == self.dma_eic_addr):
            if(self.get_dma_done_status()):
                self.clear_dma_done_irq()
                self.vic_ack_current_vector()
                if verbose:
                    print("DMA EIC irq source : %.8X"%self.get_dma_eic_src())
            else:
                raise FmcAdc100mSpecOperationError("Bad IRQ source : 0x%08X"%irq_src)
        else:
            raise FmcAdc100mSpecOperationError("Bad IRQ vector. expected:0x%08X got:0x%08X" %(self.dma_eic_addr, irq_vect))
        if verbose:
            print('DMA finished!')

    # Reads data from carrier's DDR memory
    def get_data(self, carrier_addr, length, verbose=False):
        # Configure DMA
        items_required = self.cfg_dma(carrier_addr, length, verbose)
        # make DMA
        self.make_dma(verbose)
        # Retrieve data from host memory
        data = []
        for i in range(items_required):
            data += self.gnum.get_memory_page(i+1)
        if verbose:
            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)
        if verbose:
            print('channels data length:%d')%(len(channels_data))
        return channels_data

    # Writes data pattern to carrier's DDR memory
    def put_data(self, carrier_addr, pattern, length, verbose=False):
        # Configure DMA
        self.cfg_dma(carrier_addr, length, verbose)
        # 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)
        # Make DMA
        self.make_dma(verbose)

    # 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 FmcAdc100mSpecOperationError('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)
        if verbose:
            self.gnum.print_dma_items()

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

        # Make DMA
        self.make_dma(verbose)


    #======================================================================
    # Data acquisition

    # 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!')



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