# ---------------------------------------------------------------------- # Copyright (c) 2016, The Regents of the University of California All # rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # * Neither the name of The Regents of the University of California # nor the names of its contributors may be used to endorse or # promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL REGENTS OF THE # UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # ---------------------------------------------------------------------- # Filename: riffa.py # Version: 2.0 # Description: Python module for RIFFA. # Author: Matthew Jacobsen # History: @mattj: Initial release. Version 2.0. import ctypes import platform NUM_FPGAS = 5 if platform.system() == "Linux": libriffa = ctypes.CDLL("libriffa.so.1") else: libriffa = ctypes.CDLL("riffa.dll") class FpgaInfoList(ctypes.Structure): _fields_ = [("num_fpgas", ctypes.c_int), ("id", ctypes.c_int * NUM_FPGAS), ("num_chnls", ctypes.c_int * NUM_FPGAS), ("name", (ctypes.c_char * 16) * NUM_FPGAS), ("vendor_id", ctypes.c_int * NUM_FPGAS), ("device_id", ctypes.c_int * NUM_FPGAS)] def __str__(self): strl = [] strl.append("num fpgas: %d" % self.num_fpgas) for i in range(self.num_fpgas): strl.append("fpga (%d) id: %d" % (i, self.id[i])) strl.append("fpga (%d) name: %s" % (i, self.name[i].value)) strl.append("fpga (%d) num_chnls: %d" % (i, self.num_chnls[i])) strl.append("fpga (%d) vendor_id: %x" % (i, self.vendor_id[i])) strl.append("fpga (%d) device_id: %x" % (i, self.device_id[i])) return '\n'.join(strl) # Populates and returns a FpgaInfoList with all FPGAs registered in the system. # Returns None on error. def fpga_list(): s = FpgaInfoList() rc = libriffa.fpga_list(ctypes.byref(s)) if (rc == 0): return s else: return None # Initializes the FPGA specified by id. On success, returns a descriptor. On # error, returns None. Each FPGA must be opened before any channels can be # accessed. Once opened, any number of threads can use the descriptor. def fpga_open(id): return libriffa.fpga_open(id) # Cleans up memory/resources for the FPGA specified by the fd descriptor. def fpga_close(fd): libriffa.fpga_close(fd) # Sends length words (4 byte words) from data to FPGA channel chnl using the # fd descriptor. The data parameter must implement the buffer protocol # interface so that a memoryview can be created from it. The FPGA channel will # be sent length, destoff, and last. If last is True, the channel should # interpret the end of this send as the end of a transaction. If last is False, # the channel should wait for additional sends before the end of the # transaction. If timeout is non-zero, this call will send data and wait up to # timeout ms for the FPGA to respond (between packets) before timing out. If # timeout is zero, this call may block indefinitely. Multiple threads sending # on the same channel may result in corrupt data or error. This function is # thread safe across channels. Returns the number of words sent. def fpga_send(fd, chnl, data, length, destoff, last, timeout): ptr = 0 datalen = 0 ilast = 0 if last: ilast = 1 if length > 0: # Pre 3.0 workaround for array types (they don't implement the 3.0 # buffer protocol interface despite documentation to the contrary). if hasattr(data, 'buffer_info') and hasattr(data, 'itemsize'): (ptr, datalen) = data.buffer_info() datalen = datalen * data.itemsize else: m = memoryview(data) obj = ctypes.py_object(m) a = ctypes.c_void_p() l = ctypes.c_size_t() ctypes.pythonapi.PyObject_AsReadBuffer(obj, ctypes.byref(a), ctypes.byref(l)) ptr = a.value datalen = l.value if length > (datalen/4): length = (datalen/4) return libriffa.fpga_send(fd, chnl, ptr, length, destoff, ilast, timeout) # Receives data from the FPGA channel chnl to the supplied data object, using # the fd descriptor. The supplied data object must implement the buffer # protocol interface so that a memoryview can be created from it. The FPGA # channel can send any amount of data, so the data object's backing array must # be large enough to accommodate. The FPGA channel will specify an offset which # will determine where in the data object's backing array the data will start # being written. If the amount of data (plus offset) exceed the size of the # data object's backing array, then additional data will be discarded. If # timeout is non-zero, this call will wait up to timeout ms for the FPGA to # respond (between packets) before timing out. If timeout is zero, this call # may block indefinitely. Multiple threads receiving on the same channel may # result in corrupt data or error. This function is thread safe across channels. # Returns the number of words received to the data array. def fpga_recv(fd, chnl, data, timeout): ptr = 0 datalen = 0 if data is not None: # Pre 3.0 workaround for array types (they don't implement the 3.0 # buffer protocol interface despite documentation to the contrary). if hasattr(data, 'buffer_info') and hasattr(data, 'itemsize'): (ptr, datalen) = data.buffer_info() datalen = datalen * data.itemsize else: m = memoryview(data) obj = ctypes.py_object(m) a = ctypes.c_void_p() l = ctypes.c_size_t() ctypes.pythonapi.PyObject_AsReadBuffer(obj, ctypes.byref(a), ctypes.byref(l)) ptr = a.value datalen = l.value return libriffa.fpga_recv(fd, chnl, ptr, datalen/4, timeout) # Resets the state of the FPGA and all transfers across all channels. This is # meant to be used as an alternative to rebooting if an error occurs while # sending/receiving. Calling this function while other threads are sending or # receiving will result in unexpected behavior. def fpga_reset(fd): libriffa.fpga_reset(fd)