From 4adffc0994c56c38dafe6a395d3ed94e8e9477cc Mon Sep 17 00:00:00 2001 From: Paul Prozesky Date: Tue, 13 Jun 2017 21:17:28 +0200 Subject: [PATCH] Refactor to allow skarab- or roach-style devices --- scripts/casperfpga_tengbe_status.py | 19 +- src/__init__.py | 4 +- src/casperfpga.py | 171 ++++-- src/fortygbe.py | 259 ++++++++ src/gbe.py | 10 + src/kcf_deprecated.py | 2 +- src/network.py | 172 ++++++ src/tengbe.py | 452 +++++--------- src/transport.py | 131 ++++ src/{katcp_fpga.py => transport_katcp.py} | 123 ++-- src/{skarab_fpga.py => transport_skarab.py} | 640 +++++++------------- 11 files changed, 1150 insertions(+), 833 deletions(-) create mode 100644 src/gbe.py create mode 100644 src/network.py create mode 100644 src/transport.py rename src/{katcp_fpga.py => transport_katcp.py} (90%) rename src/{skarab_fpga.py => transport_skarab.py} (87%) diff --git a/scripts/casperfpga_tengbe_status.py b/scripts/casperfpga_tengbe_status.py index 140f3f93..5a7b50e1 100644 --- a/scripts/casperfpga_tengbe_status.py +++ b/scripts/casperfpga_tengbe_status.py @@ -2,13 +2,7 @@ # -*- coding: utf-8 -*- # pylint: disable-msg=C0103 # pylint: disable-msg=C0301 -""" -View the status of a given xengine. -Created on Fri Jan 3 10:40:53 2014 - -@author: paulp -""" import sys import time import argparse @@ -53,7 +47,8 @@ except AttributeError: raise RuntimeError('No such log level: %s' % log_level) # import logging -# logging.basicConfig(filename='/tmp/casperfpga_tengbe_status_curses.log', level=logging.DEBUG) +# logging.basicConfig(filename='/tmp/casperfpga_tengbe_status_curses.log', +# level=logging.DEBUG) # logging.info('****************************************************') if args.comms == 'katcp': @@ -75,7 +70,7 @@ utils.threaded_fpga_function(fpgas, 10, 'test_connection') utils.threaded_fpga_function(fpgas, 15, 'get_system_information') for fpga in fpgas: - numgbes = len(fpga.tengbes) + numgbes = len(fpga.gbes) if numgbes < 1: raise RuntimeWarning('Host %s has no 10gbe cores', fpga.host) print '%s: found %i 10gbe core%s.' % ( @@ -100,7 +95,7 @@ def get_gbe_data(fpga): Get 10gbe data counters from the fpga. """ returndata = {} - for gbecore in fpga.tengbes: + for gbecore in fpga.gbes: ctr_data = gbecore.read_counters() for regname in ctr_data: regdata = ctr_data[regname] @@ -118,8 +113,8 @@ def get_tap_data(fpga): What it says on the tin. """ data = {} - for gbecore in fpga.tengbes.names(): - data[gbecore] = fpga.tengbes[gbecore].tap_info() + for gbecore in fpga.gbes.names(): + data[gbecore] = fpga.gbes[gbecore].tap_info() return data # get gbe and tap data @@ -164,7 +159,7 @@ def exit_gracefully(sig, frame): max_1st_col_offset = -1 for fpga in fpgas: max_1st_col_offset = max(max_1st_col_offset, len(fpga.host)) - for gbe_name in fpga.tengbes.names(): + for gbe_name in fpga.gbes.names(): max_1st_col_offset = max(max_1st_col_offset, len(gbe_name)) max_fldname = -1 for hdr in fpga_headers: diff --git a/src/__init__.py b/src/__init__.py index 1fea50ef..774e7aea 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -6,14 +6,14 @@ from bitfield import Bitfield, Field from dcp_fpga import DcpFpga from katadc import KatAdc -from katcp_fpga import KatcpFpga +from transport_katcp import KatcpTransport +from transport_skarab import SkarabTransport from memory import Memory from qdr import Qdr from register import Register from sbram import Sbram from snap import Snap from tengbe import TenGbe -from skarab_fpga import SkarabFpga # BEGIN VERSION CHECK # Get package version when locally imported from repo or via -e develop install diff --git a/src/casperfpga.py b/src/casperfpga.py index e4911afd..eb012f50 100644 --- a/src/casperfpga.py +++ b/src/casperfpga.py @@ -1,19 +1,22 @@ -""" -Created on Feb 28, 2013 - -@author: paulp -""" import logging import struct import time +import socket +import select import register import sbram import snap import tengbe +import fortygbe import qdr from attribute_container import AttributeContainer from utils import parse_fpg +import skarab_definitions as skarab_defs + +from transport_katcp import KatcpTransport +from transport_skarab import SkarabTransport + LOGGER = logging.getLogger(__name__) @@ -23,7 +26,8 @@ 'xps:bram': {'class': sbram.Sbram, 'container': 'sbrams'}, 'xps:qdr': {'class': qdr.Qdr, 'container': 'qdrs'}, 'xps:sw_reg': {'class': register.Register, 'container': 'registers'}, - 'xps:tengbe_v2': {'class': tengbe.TenGbe, 'container': 'tengbes'}, + 'xps:tengbe_v2': {'class': tengbe.TenGbe, 'container': 'gbes'}, + 'xps:forty_gbe': {'class': fortygbe.FortyGbe, 'container': 'gbes'}, 'casper:snapshot': {'class': snap.Snap, 'container': 'snapshots'}, } @@ -51,17 +55,46 @@ } +def choose_transport(host_ip): + """ + Test whether a given host is a katcp client or a skarab + :param host_ip: + :return: + """ + skarab_ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + skarab_ctrl_sock.setblocking(0) + skarab_eth_ctrl_port = (host_ip, skarab_defs.ETHERNET_CONTROL_PORT_ADDRESS) + request = skarab_defs.ReadRegReq( + 0, skarab_defs.BOARD_REG, skarab_defs.C_RD_VERSION_ADDR) + skarab_ctrl_sock.sendto(request.create_payload(), skarab_eth_ctrl_port) + data_ready = select.select([skarab_ctrl_sock], [], [], 0.1) + skarab_ctrl_sock.close() + if len(data_ready[0]) > 0: + logging.debug('%s seems to be a SKARAB' % host_ip) + return SkarabTransport + logging.debug('%s seems to be a ROACH' % host_ip) + return KatcpTransport + + class CasperFpga(object): """ A FPGA host board that has a CASPER design running on it. Or will soon have. """ - def __init__(self, host): + def __init__(self, host, transport=None): """ - :param host: the hostname of this CasperFpga :return: """ self.host = host + self.transport = None + + if transport: + self.transport = transport(host) + else: + transport_class = choose_transport(host) + self.transport = transport_class(host) + + # self.transport = choose_transport(host) # this is just for code introspection self.devices = None @@ -70,27 +103,37 @@ def __init__(self, host): self.sbrams = None self.qdrs = None self.registers = None - self.tengbes = None + self.gbes = None self.snapshots = None self.system_info = None self.rcs_info = None # /just for introspection - self.__reset_device_info() + self._reset_device_info() LOGGER.debug('%s: now a CasperFpga' % self.host) + def connect(self, timeout=None): + return self.transport.connect(timeout) + + def disconnect(self): + return self.transport.disconnect() + def read(self, device_name, size, offset=0): - raise NotImplementedError + return self.transport.read(device_name, size, offset) def blindwrite(self, device_name, data, offset=0): - raise NotImplementedError + return self.transport.blindwrite(device_name, data, offset) def listdev(self): """ Get a list of the memory bus items in this design. :return: a list of memory devices """ - raise NotImplementedError + try: + return self.transport.listdev() + except AttributeError: + return self.memory_devices.keys() + raise RuntimeError def deprogram(self): """ @@ -98,7 +141,11 @@ def deprogram(self): device information :return: """ - self.__reset_device_info() + self.transport.deprogram() + self._reset_device_info() + + def set_igmp_version(self): + return self.transport.set_igmp_version() def upload_to_ram_and_program(self, filename, port=-1, timeout=10, wait_complete=True): @@ -111,9 +158,19 @@ def upload_to_ram_and_program(self, filename, port=-1, timeout=10, after upload if False :return: """ - raise NotImplementedError + rv = self.transport.upload_to_ram_and_program( + filename, port, timeout, wait_complete) + self.get_system_information(filename) + return rv + + def is_running(self): + """ + Is the FPGA programmed and running? + :return: + """ + return self.transport.is_running() - def __reset_device_info(self): + def _reset_device_info(self): """ Reset information of devices this FPGA knows about. """ @@ -145,18 +202,27 @@ def test_connection(self): (self.host, rval, val)) return True -# def __getattribute__(self, name): -# if name == 'registers': -# return {self.memory_devices[r].name: self.memory_devices[r] for r in self.memory_devices_memory['register']['items']} -# return object.__getattribute__(self, name) + # def __getattribute__(self, name): + # if name == 'registers': + # return {self.memory_devices[r].name: self.memory_devices[r] + # for r in self.memory_devices_memory['register']['items']} + # return object.__getattribute__(self, name) + + def dram_bulkread(self, device, size, offset): + """ + + :return: + """ + raise NotImplementedError def read_dram(self, size, offset=0): """ Reads data from a ROACH's DRAM. Reads are done up to 1MB at a time. - The 64MB indirect address register is automatically incremented as necessary. + The 64MB indirect address register is automatically incremented + as necessary. It returns a string, as per the normal 'read' function. ROACH has a fixed device name for the DRAM (dram memory). - Uses bulkread internally. + Uses dram_bulkread internally. :param size: amount of data to read, in bytes :param offset: offset at which to read, in bytes :return: binary data string @@ -172,12 +238,16 @@ def read_dram(self, size, offset=0): while n_reads < size: dram_page = (offset + n_reads) / dram_indirect_page_size local_offset = (offset + n_reads) % dram_indirect_page_size - # local_reads = min(read_chunk_size, size-n_reads, dram_indirect_page_size-(offset%dram_indirect_page_size)) - local_reads = min(size - n_reads, dram_indirect_page_size - (offset % dram_indirect_page_size)) + # local_reads = min(read_chunk_size, size - n_reads, + # dram_indirect_page_size - + # (offset % dram_indirect_page_size)) + local_reads = min(size - n_reads, dram_indirect_page_size - + (offset % dram_indirect_page_size)) if last_dram_page != dram_page: self.write_int('dram_controller', dram_page) last_dram_page = dram_page - local_data = (self.bulkread('dram_memory', local_reads, local_offset)) + local_data = self.dram_bulkread('dram_memory', + local_reads, local_offset) data.append(local_data) LOGGER.debug('%s: reading %8i bytes from indirect ' 'address %4i at local offset %8i... done.' % @@ -188,9 +258,10 @@ def read_dram(self, size, offset=0): def write_dram(self, data, offset=0): """ Writes data to a ROACH's DRAM. Writes are done up to 512KiB at a time. - The 64MB indirect address register is automatically incremented as necessary. - ROACH has a fixed device name for the DRAM (dram memory) and so the user does not need to specify the write - register. + The 64MB indirect address register is automatically + incremented as necessary. + ROACH has a fixed device name for the DRAM (dram memory) and so the + user does not need to specify the write register. :param data: packed binary string data to write :param offset: the offset at which to write :return: @@ -207,15 +278,18 @@ def write_dram(self, data, offset=0): while n_writes < size: dram_page = (offset+n_writes)/dram_indirect_page_size local_offset = (offset+n_writes) % dram_indirect_page_size - local_writes = min(write_chunk_size, size-n_writes, - dram_indirect_page_size-(offset % dram_indirect_page_size)) - LOGGER.debug('%s: writing %8i bytes from indirect address %4i at local offset %8i...' % - (self.host, local_writes, dram_page, local_offset)) + local_writes = min(write_chunk_size, size - n_writes, + dram_indirect_page_size - + (offset % dram_indirect_page_size)) + LOGGER.debug('%s: writing %8i bytes from indirect address %4i ' + 'at local offset %8i...' % ( + self.host, local_writes, dram_page, local_offset)) if last_dram_page != dram_page: self.write_int('dram_controller', dram_page) last_dram_page = dram_page - - self.blindwrite('dram_memory', data[n_writes:n_writes+local_writes], local_offset) + self.blindwrite( + 'dram_memory', + data[n_writes:n_writes + local_writes], local_offset) n_writes += local_writes def write(self, device_name, data, offset=0): @@ -243,7 +317,8 @@ def write(self, device_name, data, offset=0): def read_int(self, device_name, word_offset=0): """ Read an integer from memory device. - i.e. calls self.read(device_name, size=4, offset=0) and uses struct to unpack it into an integer + i.e. calls self.read(device_name, size=4, offset=0) and uses + struct to unpack it into an integer :param device_name: device from which to read :param word_offset: the 32-bit word offset at which to read :return: signed 32-bit integer @@ -330,7 +405,7 @@ def write_int(self, device_name, integer, blindwrite=False, word_offset=0): # rv['app_rev'] = app & ((2 ** 28)-1) # return rv - def __create_memory_devices(self, device_dict, memorymap_dict): + def _create_memory_devices(self, device_dict, memorymap_dict): """ Create memory devices from dictionaries of design information. :param device_dict: raw dictionary of information from tagged @@ -377,7 +452,7 @@ def __create_memory_devices(self, device_dict, memorymap_dict): except AttributeError: # the device may not have an update function pass - def __create_other_devices(self, device_dict): + def _create_other_devices(self, device_dict): """ Store non-memory device information in a dictionary :param device_dict: raw dictionary of information from tagged @@ -418,6 +493,8 @@ def get_system_information(self, filename=None, fpg_info=None): dictionaries :return: the information is populated in the class """ + filename, fpg_info = self.transport.get_system_information( + filename, fpg_info) if (filename is None) and (fpg_info is None): raise RuntimeError('Either filename or parsed fpg data ' 'must be given.') @@ -427,11 +504,12 @@ def get_system_information(self, filename=None, fpg_info=None): device_dict = fpg_info[0] memorymap_dict = fpg_info[1] # add system registers - device_dict.update(self.__add_sys_registers()) - # reset current devices, create new ones from the new design information - self.__reset_device_info() - self.__create_memory_devices(device_dict, memorymap_dict) - self.__create_other_devices(device_dict) + device_dict.update(self._add_sys_registers()) + # reset current devices and create new ones from the new + # design information + self._reset_device_info() + self._create_memory_devices(device_dict, memorymap_dict) + self._create_other_devices(device_dict) # populate some system information try: self.system_info.update(device_dict['77777']) @@ -446,6 +524,9 @@ def get_system_information(self, filename=None, fpg_info=None): self.rcs_info['git'][name] = device_dict[device_name] if '77777_svn' in device_dict: self.rcs_info['svn'] = device_dict['77777_svn'] + self.transport.memory_devices = self.memory_devices + self.transport.gbes = self.gbes + self.transport.post_get_system_information() def estimate_fpga_clock(self): """ @@ -466,7 +547,7 @@ def check_tx_raw(self, wait_time=0.2, checks=10): :param checks: times to run check :return: """ - for gbecore in self.tengbes: + for gbecore in self.gbes: if not gbecore.tx_okay(wait_time=wait_time, checks=checks): return False return True @@ -479,13 +560,13 @@ def check_rx_raw(self, wait_time=0.2, checks=10): :param checks: times to run check :return: """ - for gbecore in self.tengbes: + for gbecore in self.gbes: if not gbecore.rx_okay(wait_time=wait_time, checks=checks): return False return True @staticmethod - def __add_sys_registers(): + def _add_sys_registers(): standard_reg = {'tag': 'xps:sw_reg', 'io_dir': 'To Processor', 'io_delay': '1', 'sample_period': '1', 'names': 'reg', 'bitwidths': '32', 'bin_pts': '0', 'arith_types': '0', diff --git a/src/fortygbe.py b/src/fortygbe.py index e69de29b..27d85c39 100644 --- a/src/fortygbe.py +++ b/src/fortygbe.py @@ -0,0 +1,259 @@ +import logging + +from network import IpAddress +from skarab_definitions import MULTICAST_REQUEST, ConfigureMulticastReq + +LOGGER = logging.getLogger(__name__) + + +class FortyGbe(object): + """ + + """ + + def __init__(self, parent, name, position, address=0x50000, + length_bytes=0x4000, device_info=None): + """ + + :param parent: + :param position: + """ + self.name = name + self.parent = parent + self.position = position + self.address = address + self.length = length_bytes + self.block_info = device_info + + @classmethod + def from_device_info(cls, parent, device_name, device_info, memorymap_dict): + """ + Process device info and the memory map to get all necessary info + and return a TenGbe instance. + :param parent: the parent device, normally an FPGA instance + :param device_name: the unique device name + :param device_info: information about this device + :param memorymap_dict: a dictionary containing the device memory map + :return: a TenGbe object + """ + # address, length_bytes = -1, -1 + # for mem_name in memorymap_dict.keys(): + # if mem_name == device_name: + # address = memorymap_dict[mem_name]['address'] + # length_bytes = memorymap_dict[mem_name]['bytes'] + # break + # if address == -1 or length_bytes == -1: + # raise RuntimeError('Could not find address or length ' + # 'for FortyGbe %s' % device_name) + address = 0x50000 + length_bytes = 0x4000 + return cls(parent, device_name, 0, address, length_bytes, device_info) + + def _wbone_rd(self, addr): + """ + + :param addr: + :return: + """ + return self.parent.read_wishbone(addr) + + def _wbone_wr(self, addr, val): + """ + + :param addr: + :param val: + :return: + """ + return self.parent.write_wishbone(addr, val) + + def enable(self): + """ + + :return: + """ + en_port = self._wbone_rd(self.address + 0x20) + if en_port >> 16 == 1: + return + en_port_new = (1 << 16) + (en_port & (2 ** 16 - 1)) + self._wbone_wr(self.address + 0x20, en_port_new) + if self._wbone_rd(self.address + 0x20) != en_port_new: + errmsg = 'Error enabling 40gbe port' + LOGGER.error(errmsg) + raise ValueError(errmsg) + + def disable(self): + """ + + :return: + """ + en_port = self._wbone_rd(self.address + 0x20) + if en_port >> 16 == 0: + return + old_port = en_port & (2 ** 16 - 1) + self._wbone_wr(self.address + 0x20, old_port) + if self._wbone_rd(self.address + 0x20) != old_port: + errmsg = 'Error disabling 40gbe port' + LOGGER.error(errmsg) + raise ValueError(errmsg) + + def get_ip(self): + """ + + :return: + """ + ip = self._wbone_rd(self.address + 0x10) + return IpAddress(ip) + + def get_port(self): + """ + + :return: + """ + en_port = self._wbone_rd(self.address + 0x20) + return en_port & (2 ** 16 - 1) + + def set_port(self, port): + """ + + :param port: + :return: + """ + en_port = self._wbone_rd(self.address + 0x20) + if en_port & (2 ** 16 - 1) == port: + return + en_port_new = ((en_port >> 16) << 16) + port + self._wbone_wr(self.address + 0x20, en_port_new) + if self._wbone_rd(self.address + 0x20) != en_port_new: + errmsg = 'Error setting 40gbe port to 0x%04x' % port + LOGGER.error(errmsg) + raise ValueError(errmsg) + + def details(self): + """ + Get the details of the ethernet mac on this device + :return: + """ + from tengbe import IpAddress, Mac + gbebase = self.address + gbedata = [] + for ctr in range(0, 0x40, 4): + gbedata.append(self._wbone_rd(gbebase + ctr)) + gbebytes = [] + for d in gbedata: + gbebytes.append((d >> 24) & 0xff) + gbebytes.append((d >> 16) & 0xff) + gbebytes.append((d >> 8) & 0xff) + gbebytes.append((d >> 0) & 0xff) + pd = gbebytes + returnval = { + 'ip_prefix': '%i.%i.%i.' % (pd[0x10], pd[0x11], pd[0x12]), + 'ip': IpAddress('%i.%i.%i.%i' % (pd[0x10], pd[0x11], + pd[0x12], pd[0x13])), + 'mac': Mac('%i:%i:%i:%i:%i:%i' % (pd[0x02], pd[0x03], pd[0x04], + pd[0x05], pd[0x06], pd[0x07])), + 'gateway_ip': IpAddress('%i.%i.%i.%i' % (pd[0x0c], pd[0x0d], + pd[0x0e], pd[0x0f])), + 'fabric_port': ((pd[0x22] << 8) + (pd[0x23])), + 'fabric_en': bool(pd[0x21] & 1), + 'xaui_lane_sync': [ + bool(pd[0x27] & 4), bool(pd[0x27] & 8), + bool(pd[0x27] & 16), bool(pd[0x27] & 32)], + 'xaui_status': [ + pd[0x24], pd[0x25], pd[0x26], pd[0x27]], + 'xaui_chan_bond': bool(pd[0x27] & 64), + 'xaui_phy': { + 'rx_eq_mix': pd[0x28], + 'rx_eq_pol': pd[0x29], + 'tx_preemph': pd[0x2a], + 'tx_swing': pd[0x2b]}, + 'multicast': { + 'base_ip': IpAddress('%i.%i.%i.%i' % (pd[0x30], pd[0x31], + pd[0x32], pd[0x33])), + 'ip_mask': IpAddress('%i.%i.%i.%i' % (pd[0x34], pd[0x35], + pd[0x36], pd[0x37])), + 'subnet_mask': IpAddress('%i.%i.%i.%i' % (pd[0x38], pd[0x39], + pd[0x3a], pd[0x3b]))} + } + possible_addresses = [int(returnval['multicast']['base_ip'])] + mask_int = int(returnval['multicast']['ip_mask']) + for ctr in range(32): + mask_bit = (mask_int >> ctr) & 1 + if not mask_bit: + new_ips = [] + for ip in possible_addresses: + new_ips.append(ip & (~(1 << ctr))) + new_ips.append(new_ips[-1] | (1 << ctr)) + possible_addresses.extend(new_ips) + returnval['multicast']['rx_ips'] = [] + tmp = list(set(possible_addresses)) + for ip in tmp: + returnval['multicast']['rx_ips'].append(IpAddress(ip)) + return returnval + + def multicast_receive(self, ip_str, group_size): + """ + Send a request to KATCP to have this tap instance send a multicast + group join request. + :param ip_str: A dotted decimal string representation of the base + mcast IP address. + :param group_size: An integer for how many mcast addresses from + base to respond to. + :return: + """ + ip = IpAddress(ip_str) + ip_high = ip.ip_int >> 16 + ip_low = ip.ip_int & 65535 + mask = IpAddress('255.255.255.%i' % (255 - group_size)) + mask_high = mask.ip_int >> 16 + mask_low = mask.ip_int & 65535 + # ip = IpAddress('239.2.0.64') + # ip_high = ip.ip_int >> 16 + # ip_low = ip.ip_int & 65535 + # mask = IpAddress('255.255.255.240') + # mask_high = mask.ip_int >> 16 + # mask_low = mask.ip_int & 65535 + request = ConfigureMulticastReq( + self.parent.seq_num, 1, ip_high, ip_low, mask_high, mask_low) + resp = self.parent.send_packet( + payload=request.create_payload(), + response_type='ConfigureMulticastResp', + expect_response=True, + command_id=MULTICAST_REQUEST, + number_of_words=11, pad_words=4) + resp_ip = IpAddress( + resp.fabric_multicast_ip_address_high << 16 | + resp.fabric_multicast_ip_address_low) + resp_mask = IpAddress( + resp.fabric_multicast_ip_address_mask_high << 16 | + resp.fabric_multicast_ip_address_mask_low) + LOGGER.debug('%s: multicast configured: addr(%s) mask(%s)' % ( + self.name, resp_ip.ip_str, resp_mask.ip_str)) + + def rx_okay(self, wait_time=0.2, checks=10): + """ + Is this gbe core receiving okay? + i.e. _rxctr incrementing and _rxerrctr not incrementing + :param wait_time: seconds to wait between checks + :param checks: times to run check + :return: True/False + """ + # TODO + raise NotImplementedError + if checks < 2: + raise RuntimeError('Cannot check less often than twice?') + fields = { + # name, required, True=same|False=different + self.name + '_rxctr': (True, False), + self.name + '_rxfullctr': (False, True), + self.name + '_rxofctr': (False, True), + self.name + '_rxerrctr': (True, True), + self.name + '_rxvldctr': (False, False), + } + result, message = check_changing_status(fields, self.read_rx_counters, + wait_time, checks) + if not result: + LOGGER.error('%s: %s' % (self.fullname, message)) + return False + return True + +# end diff --git a/src/gbe.py b/src/gbe.py new file mode 100644 index 00000000..a054506a --- /dev/null +++ b/src/gbe.py @@ -0,0 +1,10 @@ +import logging + +LOGGER = logging.getLogger(__name__) + + +class Gbe(object): + """ + A (multi)gigabit network interface on a device. + """ + pass diff --git a/src/kcf_deprecated.py b/src/kcf_deprecated.py index 36c977e6..b444003d 100644 --- a/src/kcf_deprecated.py +++ b/src/kcf_deprecated.py @@ -96,7 +96,7 @@ # self.registers = AttributeContainer() # self.snapshots = AttributeContainer() # self.sbrams = AttributeContainer() -# self.tengbes = AttributeContainer() +# self.gbes = AttributeContainer() # self.katadcs = AttributeContainer() # self.qdrs = AttributeContainer() # diff --git a/src/network.py b/src/network.py new file mode 100644 index 00000000..d59abf21 --- /dev/null +++ b/src/network.py @@ -0,0 +1,172 @@ +import struct + + +class Mac(object): + """ + A MAC address. + """ + + @staticmethod + def mac2str(mac): + """ + Convert a MAC in integer form to a human-readable string. + """ + mac_pieces = [(mac & ((1 << 48) - (1 << 40))) >> 40, + (mac & ((1 << 40) - (1 << 32))) >> 32, + (mac & ((1 << 32) - (1 << 24))) >> 24, + (mac & ((1 << 24) - (1 << 16))) >> 16, + (mac & ((1 << 16) - (1 << 8))) >> 8, + (mac & ((1 << 8) - (1 << 0))) >> 0] + return '%02X:%02X:%02X:%02X:%02X:%02X' % (mac_pieces[0], mac_pieces[1], + mac_pieces[2], mac_pieces[3], + mac_pieces[4], mac_pieces[5]) + + @staticmethod + def str2mac(mac_str): + """ + Convert a human-readable MAC string to an integer. + """ + mac = 0 + if mac_str.count(':') != 5: + raise RuntimeError('A MAC address must be of the' + ' form xx:xx:xx:xx:xx:xx, got %s' % mac_str) + offset = 40 + for byte_str in mac_str.split(':'): + value = int(byte_str, base=16) + mac += value << offset + offset -= 8 + return mac + + def __init__(self, mac): + mac_str = None + mac_int = None + if isinstance(mac, Mac): + mac_str = str(mac) + elif isinstance(mac, basestring): + mac_str = mac + elif isinstance(mac, int): + mac_int = mac + if mac_str is not None: + if mac_str.find(':') == -1: + mac_int = int(mac_str) + mac_str = None + if (mac_str is None) and (mac_int is None): + raise ValueError('Cannot make a MAC with no value.') + elif mac_str is not None: + self.mac_int = self.str2mac(mac_str) + self.mac_str = mac_str + elif mac_int is not None: + self.mac_str = self.mac2str(mac_int) + self.mac_int = mac_int + + @classmethod + def from_roach_hostname(cls, hostname, port_num): + """ + Make a MAC address object from a ROACH hostname + """ + # HACK + if hostname.startswith('cbf_oach') or hostname.startswith('dsim'): + hostname = hostname.replace('cbf_oach', 'roach') + hostname = hostname.replace('dsim', 'roach') + # /HACK + if not hostname.startswith('roach'): + raise RuntimeError('Only hostnames beginning with ' + 'roach supported: %s' % hostname) + digits = hostname.replace('roach', '') + serial = [int(digits[ctr:ctr+2], 16) for ctr in range(0, 6, 2)] + mac_str = 'fe:00:%02x:%02x:%02x:%02x' % (serial[0], serial[1], + serial[2], port_num) + return cls(mac_str) + + def packed(self): + mac = [0, 0] + for byte in self.mac_str.split(':'): + mac.extend([int(byte, base=16)]) + return struct.pack('>8B', *mac) + + def __int__(self): + return self.mac_int + + def __str__(self): + return self.mac_str + + def __repr__(self): + return 'Mac(%s)' % self.__str__() + + +class IpAddress(object): + """ + An IP address. + """ + @staticmethod + def ip2str(ip_addr): + """ + Convert an IP in integer form to a human-readable string. + """ + ip_pieces = [ip_addr / (2 ** 24), + ip_addr % (2 ** 24) / (2 ** 16), + ip_addr % (2 ** 16) / (2 ** 8), + ip_addr % (2 ** 8)] + return '%i.%i.%i.%i' % (ip_pieces[0], ip_pieces[1], + ip_pieces[2], ip_pieces[3]) + + @staticmethod + def str2ip(ip_str): + """ + Convert a human-readable IP string to an integer. + """ + ip_addr = 0 + if ip_str.count('.') != 3: + raise RuntimeError('An IP address must be of ' + 'the form xxx.xxx.xxx.xxx') + offset = 24 + for octet in ip_str.split('.'): + value = int(octet) + ip_addr += value << offset + offset -= 8 + return ip_addr + + def __init__(self, ip): + ip_str = None + ip_int = None + if isinstance(ip, IpAddress): + ip_str = str(ip) + elif isinstance(ip, basestring): + ip_str = ip + elif isinstance(ip, int): + ip_int = ip + if ip_str is not None: + if ip_str.find('.') == -1: + ip_int = int(ip_str) + ip_str = None + if (ip_str is None) and (ip_int is None): + raise ValueError('Cannot make an IP with no value.') + elif ip_str is not None: + self.ip_int = self.str2ip(ip_str) + self.ip_str = ip_str + elif ip_int is not None: + self.ip_str = self.ip2str(ip_int) + self.ip_int = ip_int + + def packed(self): + ip = [] + for byte in self.ip_str.split('.'): + ip.extend([int(byte, base=10)]) + return struct.pack('>4B', *ip) + + def is_multicast(self): + """ + Does the data source's IP address begin with 239? + :return: + """ + return (self.ip_int >> 24) == 239 + + def __int__(self): + return self.ip_int + + def __str__(self): + return self.ip_str + + def __repr__(self): + return 'IpAddress(%s)' % self.__str__() + diff --git a/src/tengbe.py b/src/tengbe.py index 4a46e26c..febef50d 100644 --- a/src/tengbe.py +++ b/src/tengbe.py @@ -1,181 +1,14 @@ -""" -Created on Feb 28, 2013 - -@author: paulp -""" - import logging import struct from utils import check_changing_status from memory import Memory +from network import Mac, IpAddress LOGGER = logging.getLogger(__name__) -class Mac(object): - """ - A MAC address. - """ - - @staticmethod - def mac2str(mac): - """ - Convert a MAC in integer form to a human-readable string. - """ - mac_pieces = [(mac & ((1 << 48) - (1 << 40))) >> 40, (mac & ((1 << 40) - (1 << 32))) >> 32, - (mac & ((1 << 32) - (1 << 24))) >> 24, (mac & ((1 << 24) - (1 << 16))) >> 16, - (mac & ((1 << 16) - (1 << 8))) >> 8, (mac & ((1 << 8) - (1 << 0))) >> 0] - return '%02X:%02X:%02X:%02X:%02X:%02X' % (mac_pieces[0], mac_pieces[1], - mac_pieces[2], mac_pieces[3], - mac_pieces[4], mac_pieces[5]) - - @staticmethod - def str2mac(mac_str): - """ - Convert a human-readable MAC string to an integer. - """ - mac = 0 - if mac_str.count(':') != 5: - raise RuntimeError('A MAC address must be of the form xx:xx:xx:xx:xx:xx, got %s' % mac_str) - offset = 40 - for byte_str in mac_str.split(':'): - value = int(byte_str, base=16) - mac += value << offset - offset -= 8 - return mac - - def __init__(self, mac): - mac_str = None - mac_int = None - if isinstance(mac, Mac): - mac_str = str(mac) - elif isinstance(mac, basestring): - mac_str = mac - elif isinstance(mac, int): - mac_int = mac - if mac_str is not None: - if mac_str.find(':') == -1: - mac_int = int(mac_str) - mac_str = None - if (mac_str is None) and (mac_int is None): - raise ValueError('Cannot make a MAC with no value.') - elif mac_str is not None: - self.mac_int = self.str2mac(mac_str) - self.mac_str = mac_str - elif mac_int is not None: - self.mac_str = self.mac2str(mac_int) - self.mac_int = mac_int - - @classmethod - def from_roach_hostname(cls, hostname, port_num): - """ - Make a MAC address object from a ROACH hostname - """ - # HACK - if hostname.startswith('cbf_oach') or hostname.startswith('dsim'): - hostname = hostname.replace('cbf_oach', 'roach') - hostname = hostname.replace('dsim', 'roach') - # /HACK - if not hostname.startswith('roach'): - raise RuntimeError('Only hostnames beginning with ' - 'roach supported: %s' % hostname) - digits = hostname.replace('roach', '') - serial = [int(digits[ctr:ctr+2], 16) for ctr in range(0, 6, 2)] - mac_str = 'fe:00:%02x:%02x:%02x:%02x' % (serial[0], serial[1], - serial[2], port_num) - return cls(mac_str) - - def packed(self): - mac = [0, 0] - for byte in self.mac_str.split(':'): - mac.extend([int(byte, base=16)]) - return struct.pack('>8B', *mac) - - def __int__(self): - return self.mac_int - - def __str__(self): - return self.mac_str - - def __repr__(self): - return 'Mac(%s)' % self.__str__() - - -class IpAddress(object): - """ - An IP address. - """ - @staticmethod - def ip2str(ip_addr): - """ - Convert an IP in integer form to a human-readable string. - """ - ip_pieces = [ip_addr / (2 ** 24), ip_addr % (2 ** 24) / (2 ** 16), ip_addr % (2 ** 16) / (2 ** 8), - ip_addr % (2 ** 8)] - return '%i.%i.%i.%i' % (ip_pieces[0], ip_pieces[1], ip_pieces[2], ip_pieces[3]) - - @staticmethod - def str2ip(ip_str): - """ - Convert a human-readable IP string to an integer. - """ - ip_addr = 0 - if ip_str.count('.') != 3: - raise RuntimeError('An IP address must be of the form xxx.xxx.xxx.xxx') - offset = 24 - for octet in ip_str.split('.'): - value = int(octet) - ip_addr += value << offset - offset -= 8 - return ip_addr - - def __init__(self, ip): - ip_str = None - ip_int = None - if isinstance(ip, IpAddress): - ip_str = str(ip) - elif isinstance(ip, basestring): - ip_str = ip - elif isinstance(ip, int): - ip_int = ip - if ip_str is not None: - if ip_str.find('.') == -1: - ip_int = int(ip_str) - ip_str = None - if (ip_str is None) and (ip_int is None): - raise ValueError('Cannot make an IP with no value.') - elif ip_str is not None: - self.ip_int = self.str2ip(ip_str) - self.ip_str = ip_str - elif ip_int is not None: - self.ip_str = self.ip2str(ip_int) - self.ip_int = ip_int - - def packed(self): - ip = [] - for byte in self.ip_str.split('.'): - ip.extend([int(byte, base=10)]) - return struct.pack('>4B', *ip) - - def is_multicast(self): - """ - Does the data source's IP address begin with 239? - :return: - """ - return (self.ip_int >> 24) == 239 - - def __int__(self): - return self.ip_int - - def __str__(self): - return self.ip_str - - def __repr__(self): - return 'IpAddress(%s)' % self.__str__() - - class TenGbe(Memory): """ To do with the CASPER ten GBE yellow block implemented on FPGAs, @@ -206,7 +39,8 @@ def __init__(self, parent, name, address, length_bytes, device_info=None): .replace('*(2^0)', '')) fabric_mac = device_info['fab_mac'] if fabric_mac.find('hex2dec') != -1: - fabric_mac = fabric_mac.replace('hex2dec(\'', '').replace('\')', '') + fabric_mac = fabric_mac.replace('hex2dec(\'', '') + fabric_mac = fabric_mac.replace('\')', '') device_info['fab_mac'] = ( fabric_mac[0:2] + ':' + fabric_mac[2:4] + ':' + fabric_mac[4:6] + ':' + fabric_mac[6:8] + ':' + @@ -214,9 +48,10 @@ def __init__(self, parent, name, address, length_bytes, device_info=None): mac = device_info['fab_mac'] ip_address = device_info['fab_ip'] port = device_info['fab_udp'] - if mac is None or ip_address is None or port is None: - raise ValueError('%s: 10Gbe interface must have mac, ip and port.' % self.fullname) - self.setup(mac, ip_address, port) + if mac is None or ip_address is None or port is None: + raise ValueError('%s: 10Gbe interface must ' + 'have mac, ip and port.' % self.fullname) + self.setup(mac, ip_address, port) self.core_details = None self.snaps = {'tx': None, 'rx': None} self.registers = {'tx': [], 'rx': []} @@ -228,7 +63,9 @@ def __init__(self, parent, name, address, length_bytes, device_info=None): @classmethod def from_device_info(cls, parent, device_name, device_info, memorymap_dict): """ - Process device info and the memory map to get all necessary info and return a TenGbe instance. + Process device info and the memory map to get all necessary info + and return a TenGbe instance. + :param parent: the parent device, normally an FPGA instance :param device_name: the unique device name :param device_info: information about this device :param memorymap_dict: a dictionary containing the device memory map @@ -237,10 +74,12 @@ def from_device_info(cls, parent, device_name, device_info, memorymap_dict): address, length_bytes = -1, -1 for mem_name in memorymap_dict.keys(): if mem_name == device_name: - address, length_bytes = memorymap_dict[mem_name]['address'], memorymap_dict[mem_name]['bytes'] + address = memorymap_dict[mem_name]['address'] + length_bytes = memorymap_dict[mem_name]['bytes'] break if address == -1 or length_bytes == -1: - raise RuntimeError('Could not find address or length for TenGbe %s' % device_name) + raise RuntimeError('Could not find address or length ' + 'for TenGbe %s' % device_name) return cls(parent, device_name, address, length_bytes, device_info) def __repr__(self): @@ -250,15 +89,25 @@ def __str__(self): """ String representation of this 10Gbe interface. """ - return '%s: MAC(%s) IP(%s) Port(%s)' % (self.name, str(self.mac), str(self.ip_address), str(self.port)) + return '%s: MAC(%s) IP(%s) Port(%s)' % ( + self.name, str(self.mac), str(self.ip_address), str(self.port)) def setup(self, mac, ipaddress, port): + """ + Set up the MAC, IP and port for this interface + :param mac: + :param ipaddress: + :param port: + :return: + """ self.mac = Mac(mac) self.ip_address = IpAddress(ipaddress) self.port = port if isinstance(port, int) else int(port) - def post_create_update(self, _): - """Update the device with information not available at creation. + def post_create_update(self, raw_device_info): + """ + Update the device with information not available at creation. + :param raw_device_info: info about this block that may be useful """ self.registers = {'tx': [], 'rx': []} for register in self.parent.registers: @@ -293,15 +142,24 @@ def _check(self): self.parent.read(self.name, 1) def read_txsnap(self): + """ + Read the TX snapshot embedded in this TenGBE yellow block + :return: + """ tmp = self.parent.memory_devices[self.name + '_txs_ss'].read(timeout=10) return tmp['data'] def read_rxsnap(self): + """ + Read the RX snapshot embedded in this TenGBE yellow block + :return: + """ tmp = self.parent.memory_devices[self.name + '_rxs_ss'].read(timeout=10) return tmp['data'] def read_rx_counters(self): - """Read all rx counters in gbe block + """ + Read all RX counters embedded in this TenGBE yellow block """ results = {} for reg in self.registers['rx']: @@ -309,7 +167,8 @@ def read_rx_counters(self): return results def read_tx_counters(self): - """Read all tx counters in gbe block + """ + Read all TX counters embedded in this TenGBE yellow block """ results = {} for reg in self.registers['tx']: @@ -317,13 +176,13 @@ def read_tx_counters(self): return results def read_counters(self): - """Read all the counters embedded in the gbe block. + """ + Read all the counters embedded in this TenGBE yellow block """ results = {} for direction in ['tx', 'rx']: for reg in self.registers[direction]: tmp = self.parent.memory_devices[reg].read() - # keyname = self.name + '_' + direction + 'ctr' results[reg] = tmp['data']['reg'] return results @@ -377,22 +236,22 @@ def tx_okay(self, wait_time=0.2, checks=10): return False return True - #def read_raw(self, **kwargs): - # # size is in bytes - # data = self.parent.read(device_name = self.name, size = self.width/8, offset = 0) - # return {'data': data} - -# def fabric_start(self): -# '''Setup the interface by writing to the fabric directly, bypassing tap. -# ''' -# if self.tap_running(): -# log_runtime_error(LOGGER, 'TAP running on %s, stop tap before accessing fabric directly.' % self.name) -# mac_location = 0x00 -# ip_location = 0x10 -# port_location = 0x22 -# self.parent.write(self.name, self.mac.packed(), mac_location) -# self.parent.write(self.name, self.ip_address.packed(), ip_location) -# #self.parent.write_int(self.name, self.port, offset = port_location) + # def fabric_start(self): + # """ + # Setup the interface by writing to the fabric directly, bypassing tap. + # :param self: + # :return: + # """ + # if self.tap_running(): + # log_runtime_error( + # LOGGER, 'TAP running on %s, stop tap before ' + # 'accessing fabric directly.' % self.name) + # mac_location = 0x00 + # ip_location = 0x10 + # port_location = 0x22 + # self.parent.write(self.name, self.mac.packed(), mac_location) + # self.parent.write(self.name, self.ip_address.packed(), ip_location) + # # self.parent.write_int(self.name, self.port, offset = port_location) def dhcp_start(self): """ @@ -429,13 +288,14 @@ def dhcp_start(self): request_args=(self.name, 'mode', '-1')) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure re-enabling ARP.' % self.name) - - # it looks like the command completed without error, so update the basic core details + # it looks like the command completed without error, so + # update the basic core details self.get_10gbe_core_details() def tap_start(self, restart=False): - """Program a 10GbE device and start the TAP driver. - @param self This object. + """ + Program a 10GbE device and start the TAP driver. + :param restart: stop before starting """ if len(self.name) > 8: raise NameError('%s: tap device identifier must be shorter than 9 ' @@ -457,7 +317,6 @@ def tap_start(self, restart=False): def tap_stop(self): """ Stop a TAP driver. - @param self This object. """ if not self.tap_running(): return @@ -472,7 +331,6 @@ def tap_stop(self): def tap_info(self): """ Get info on the tap instance running on this interface. - @param self This object. """ uninforms = [] @@ -493,15 +351,16 @@ def handle_inform(msg): else: raise RuntimeError('%s: invalid return from tap-info?' % self.fullname) - # TODO - this request should return okay if the tap isn't running - it shouldn't fail + # TODO - this request should return okay if the tap isn't + # running - it shouldn't fail # if reply.arguments[0] != 'ok': - # log_runtime_error(LOGGER, "Failure getting tap info for device %s." % str(self)) + # log_runtime_error(LOGGER, 'Failure getting tap info for ' + # 'device %s." % str(self)) def tap_running(self): """ Determine if an instance if tap is already running on for this ten GBE interface. - @param self This object. """ tapinfo = self.tap_info() if tapinfo['name'] == '': @@ -524,15 +383,14 @@ def multicast_receive(self, ip_str, group_size): """ Send a request to KATCP to have this tap instance send a multicast group join request. - @param self This object. - @param ip_str A dotted decimal string representation of the base + :param ip_str: A dotted decimal string representation of the base mcast IP address. - @param group_size An integer for how many mcast addresses from + :param group_size: An integer for how many mcast addresses from base to respond to. """ - #mask = 255*(2 ** 24) + 255*(2 ** 16) + 255*(2 ** 8) + (255-group_size) - #self.parent.write_int(self.name, str2ip(ip_str), offset=12) - #self.parent.write_int(self.name, mask, offset=13) + # mask = 255*(2 ** 24) + 255*(2 ** 16) + 255*(2 ** 8) + (255-group_size) + # self.parent.write_int(self.name, str2ip(ip_str), offset=12) + # self.parent.write_int(self.name, mask, offset=13) # mcast_group_string = ip_str + '+' + str(group_size) mcast_group_string = ip_str @@ -551,8 +409,7 @@ def multicast_receive(self, ip_str, group_size): def multicast_remove(self, ip_str): """ Send a request to be removed from a multicast group. - @param self This object. - @param ip_str A dotted decimal string representation of the base + :param ip_str: A dotted decimal string representation of the base mcast IP address. """ try: @@ -605,7 +462,8 @@ def fabric_soft_reset_toggle(self): Toggle the fabric soft reset :return: """ - word_bytes = list(struct.unpack('>4B', self.parent.read(self.name, 4, 0x20))) + word_bytes = struct.unpack('>4B', self.parent.read(self.name, 4, 0x20)) + word_bytes = list(word_bytes) def write_val(val): word_bytes[0] = val @@ -678,42 +536,33 @@ def get_10gbe_core_details(self, read_arp=False, read_cpu=False): #self.add_field(Bitfield.Field('buffer_rx', 0, 0x1000 * word_width, 0, 0x2000 * word_width)) #self.add_field(Bitfield.Field('arp_table', 0, 0x1000 * word_width, 0, 0x3000 * word_width)) """ - returnval = {} - port_dump = list(struct.unpack('>16384B', - self.parent.read(self.name, 16384))) - returnval['ip_prefix'] = '%i.%i.%i.' % ( - port_dump[0x10], port_dump[0x11], port_dump[0x12]) - returnval['ip'] = IpAddress('%i.%i.%i.%i' % ( - port_dump[0x10], port_dump[0x11], port_dump[0x12], port_dump[0x13])) - returnval['mac'] = Mac('%i:%i:%i:%i:%i:%i' % ( - port_dump[0x02], port_dump[0x03], - port_dump[0x04], port_dump[0x05], - port_dump[0x06], port_dump[0x07])) - returnval['gateway_ip'] = IpAddress('%i.%i.%i.%i' % ( - port_dump[0x0c], port_dump[0x0d], port_dump[0x0e], port_dump[0x0f])) - returnval['fabric_port'] = ((port_dump[0x22] << 8) + (port_dump[0x23])) - returnval['fabric_en'] = bool(port_dump[0x21] & 1) - returnval['xaui_lane_sync'] = [ - bool(port_dump[0x27] & 4), bool(port_dump[0x27] & 8), - bool(port_dump[0x27] & 16), bool(port_dump[0x27] & 32)] - returnval['xaui_status'] = [ - port_dump[0x24], port_dump[0x25], port_dump[0x26], port_dump[0x27]] - returnval['xaui_chan_bond'] = bool(port_dump[0x27] & 64) - returnval['xaui_phy'] = {} - returnval['xaui_phy']['rx_eq_mix'] = port_dump[0x28] - returnval['xaui_phy']['rx_eq_pol'] = port_dump[0x29] - returnval['xaui_phy']['tx_preemph'] = port_dump[0x2a] - returnval['xaui_phy']['tx_swing'] = port_dump[0x2b] - returnval['multicast'] = {} - returnval['multicast']['base_ip'] = IpAddress( - '%i.%i.%i.%i' % (port_dump[0x30], port_dump[0x31], - port_dump[0x32], port_dump[0x33])) - returnval['multicast']['ip_mask'] = IpAddress( - '%i.%i.%i.%i' % (port_dump[0x34], port_dump[0x35], - port_dump[0x36], port_dump[0x37])) - returnval['multicast']['subnet_mask'] = IpAddress( - '%i.%i.%i.%i' % (port_dump[0x38], port_dump[0x39], - port_dump[0x3a], port_dump[0x3b])) + data = self.parent.read(self.name, 16384) + data = list(struct.unpack('>16384B', data)) + returnval = { + 'ip_prefix': '%i.%i.%i.' % (data[0x10], data[0x11], data[0x12]), + 'ip': IpAddress('%i.%i.%i.%i' % (data[0x10], data[0x11], + data[0x12], data[0x13])), + 'mac': Mac('%i:%i:%i:%i:%i:%i' % (data[0x02], data[0x03], + data[0x04], data[0x05], + data[0x06], data[0x07])), + 'gateway_ip': IpAddress('%i.%i.%i.%i' % (data[0x0c], data[0x0d], + data[0x0e], data[0x0f])), + 'fabric_port': ((data[0x22] << 8) + (data[0x23])), + 'fabric_en': bool(data[0x21] & 1), + 'xaui_lane_sync': [bool(data[0x27] & 4), bool(data[0x27] & 8), + bool(data[0x27] & 16), bool(data[0x27] & 32)], + 'xaui_status': [data[0x24], data[0x25], data[0x26], data[0x27]], + 'xaui_chan_bond': bool(data[0x27] & 64), + 'xaui_phy': {'rx_eq_mix': data[0x28], 'rx_eq_pol': data[0x29], + 'tx_preemph': data[0x2a], 'tx_swing': data[0x2b]}, + 'multicast': {'base_ip': IpAddress('%i.%i.%i.%i' % ( + data[0x30], data[0x31], data[0x32], data[0x33])), + 'ip_mask': IpAddress('%i.%i.%i.%i' % ( + data[0x34], data[0x35], data[0x36], data[0x37])), + 'subnet_mask': IpAddress('%i.%i.%i.%i' % ( + data[0x38], data[0x39], data[0x3a], data[0x3b])), + 'rx_ips': []} + } possible_addresses = [int(returnval['multicast']['base_ip'])] mask_int = int(returnval['multicast']['ip_mask']) for ctr in range(32): @@ -724,29 +573,29 @@ def get_10gbe_core_details(self, read_arp=False, read_cpu=False): new_ips.append(ip & (~(1 << ctr))) new_ips.append(new_ips[-1] | (1 << ctr)) possible_addresses.extend(new_ips) - returnval['multicast']['rx_ips'] = [] tmp = list(set(possible_addresses)) for ip in tmp: returnval['multicast']['rx_ips'].append(IpAddress(ip)) if read_arp: - returnval['arp'] = self.get_arp_details(port_dump) + returnval['arp'] = self.get_arp_details(data) if read_cpu: - returnval.update(self.get_cpu_details(port_dump)) + returnval.update(self.get_cpu_details(data)) self.core_details = returnval return returnval def get_arp_details(self, port_dump=None): """ Get ARP details from this interface. - @param port_dump: list - A list of raw bytes from interface memory. + :param port_dump: list - A list of raw bytes from interface memory. """ if port_dump is None: - port_dump = list(struct.unpack('>16384B', self.parent.read(self.name, 16384))) + port_dump = self.parent.read(self.name, 16384) + port_dump = list(struct.unpack('>16384B', port_dump)) returnval = [] for addr in range(256): mac = [] for ctr in range(2, 8): - mac.append(port_dump[0x3000+(addr*8)+ctr]) + mac.append(port_dump[0x3000 + (addr * 8) + ctr]) returnval.append(mac) return returnval @@ -757,51 +606,54 @@ def get_cpu_details(self, port_dump=None): :return: """ if port_dump is None: - port_dump = list(struct.unpack('>16384B', self.parent.read(self.name, 16384))) + port_dump = self.parent.read(self.name, 16384) + port_dump = list(struct.unpack('>16384B', port_dump)) returnval = {'cpu_tx': {}} - for ctr in range(4096/8): + for ctr in range(4096 / 8): tmp = [] - for j in range(8): - tmp.append(port_dump[4096+(8*ctr)+j]) + for ctr2 in range(8): + tmp.append(port_dump[4096 + (8 * ctr) + ctr2]) returnval['cpu_tx'][ctr*8] = tmp - returnval['cpu_rx_buf_unack_data'] = port_dump[6*4+3] + returnval['cpu_rx_buf_unack_data'] = port_dump[6 * 4 + 3] returnval['cpu_rx'] = {} - for ctr in range(port_dump[6*4+3]+8): + for ctr in range(port_dump[6 * 4 + 3] + 8): tmp = [] - for j in range(8): - tmp.append(port_dump[8192+(8*ctr)+j]) - returnval['cpu_rx'][ctr*8] = tmp + for ctr2 in range(8): + tmp.append(port_dump[8192 + (8 * ctr) + ctr2]) + returnval['cpu_rx'][ctr * 8] = tmp return returnval def print_10gbe_core_details(self, arp=False, cpu=False, refresh=True): - """Prints 10GbE core details. - @param arp boolean: Include the ARP table - @param cpu boolean: Include the CPU packet buffers + """ + Prints 10GbE core details. + :param arp: boolean, include the ARP table + :param cpu: boolean, include the CPU packet buffers + :param refresh: read the 10gbe details first """ if refresh or (self.core_details is None): self.get_10gbe_core_details(arp, cpu) - #print self.core_details + details = self.core_details print '------------------------' print '%s configuration:' % self.fullname - print 'MAC: ', Mac.mac2str(int(self.core_details['mac'])) - print 'Gateway: ', self.core_details['gateway_ip'] - print 'IP: ', self.core_details['ip'] + print 'MAC: ', Mac.mac2str(int(details['mac'])) + print 'Gateway: ', details['gateway_ip'] + print 'IP: ', details['ip'] print 'Fabric port: ', - print '%5d' % self.core_details['fabric_port'] - print 'Fabric interface is currently:', 'Enabled' if self.core_details['fabric_en'] else 'Disabled' - print 'XAUI Status: ', self.core_details['xaui_status'] - for i in range(0, 4): - print '\tlane sync %i: %i' % (i, self.core_details['xaui_lane_sync'][i]) - print '\tChannel bond: %i' % self.core_details['xaui_chan_bond'] + print '%5d' % details['fabric_port'] + print 'Fabric interface is currently:', 'Enabled' if \ + details['fabric_en'] else 'Disabled' + print 'XAUI Status: ', details['xaui_status'] + for ctr in range(0, 4): + print '\tlane sync %i: %i' % (ctr, details['xaui_lane_sync'][ctr]) + print '\tChannel bond: %i' % details['xaui_chan_bond'] print 'XAUI PHY config: ' - print '\tRX_eq_mix: %2X' % self.core_details['xaui_phy']['rx_eq_mix'] - print '\tRX_eq_pol: %2X' % self.core_details['xaui_phy']['rx_eq_pol'] - print '\tTX_pre-emph: %2X' % self.core_details['xaui_phy']['tx_preemph'] - print '\tTX_diff_ctrl: %2X' % self.core_details['xaui_phy']['tx_swing'] + print '\tRX_eq_mix: %2X' % details['xaui_phy']['rx_eq_mix'] + print '\tRX_eq_pol: %2X' % details['xaui_phy']['rx_eq_pol'] + print '\tTX_pre-emph: %2X' % details['xaui_phy']['tx_preemph'] + print '\tTX_diff_ctrl: %2X' % details['xaui_phy']['tx_swing'] print 'Multicast:' - for k in self.core_details['multicast']: - print '\t%s: %s' % (k, self.core_details['multicast'][k]) - + for k in details['multicast']: + print '\t%s: %s' % (k, details['multicast'][k]) if arp: self.print_arp_details(refresh=refresh, only_hits=True) if cpu: @@ -814,9 +666,10 @@ def print_arp_details(self, refresh=False, only_hits=False): :param only_hits: :return: """ - if self.core_details is None: + details = self.core_details + if details is None: refresh = True - elif 'arp' not in self.core_details.keys(): + elif 'arp' not in details.keys(): refresh = True if refresh: self.get_10gbe_core_details(read_arp=True) @@ -825,16 +678,16 @@ def print_arp_details(self, refresh=False, only_hits=False): all_fs = True if only_hits: for mac in range(0, 6): - if self.core_details['arp'][ip_address][mac] != 255: + if details['arp'][ip_address][mac] != 255: all_fs = False break printmac = True if only_hits and all_fs: printmac = False if printmac: - print 'IP: %s%3d: MAC:' % (self.core_details['ip_prefix'], ip_address), + print 'IP: %s%3d: MAC:' % (details['ip_prefix'], ip_address), for mac in range(0, 6): - print '%02X' % self.core_details['arp'][ip_address][mac], + print '%02X' % details['arp'][ip_address][mac], print '' def print_cpu_details(self, refresh=False): @@ -843,15 +696,16 @@ def print_cpu_details(self, refresh=False): :param refresh: :return: """ - if self.core_details is None: + details = self.core_details + if details is None: refresh = True - elif 'cpu_rx' not in self.core_details.keys(): + elif 'cpu_rx' not in details.keys(): refresh = True if refresh: self.get_10gbe_core_details(read_cpu=True) print 'CPU TX Interface (at offset 4096 bytes):' print 'Byte offset: Contents (Hex)' - for key, value in self.core_details['cpu_tx'].iteritems(): + for key, value in details['cpu_tx'].iteritems(): print '%04i: ' % key, for val in value: print '%02x' % val, @@ -859,16 +713,14 @@ def print_cpu_details(self, refresh=False): print '------------------------' print 'CPU RX Interface (at offset 8192bytes):' - print 'CPU packet RX buffer unacknowledged data: %i' % self.core_details['cpu_rx_buf_unack_data'] + print 'CPU packet RX buffer unacknowledged data: ' \ + '%i' % details['cpu_rx_buf_unack_data'] print 'Byte offset: Contents (Hex)' - for key, value in self.core_details['cpu_rx'].iteritems(): + for key, value in details['cpu_rx'].iteritems(): print '%04i: ' % key, for val in value: print '%02x' % val, print '' print '------------------------' - -# some helper functions - # end diff --git a/src/transport.py b/src/transport.py new file mode 100644 index 00000000..7e8e23b7 --- /dev/null +++ b/src/transport.py @@ -0,0 +1,131 @@ +class Transport(object): + """ + The actual network transport of data for a CasperFpga object. + """ + def __init__(self, host): + """ + + :param host: + """ + self.host = host + self.memory_devices = {} + self.gbes = [] + self.prog_info = {'last_uploaded': '', 'last_programmed': '', + 'system_name': ''} + + def connect(self, timeout=None): + """ + + :param timeout: + :return: + """ + raise NotImplementedError + + def is_running(self): + """ + Is the FPGA programmed and running? + :return: True or False + """ + raise NotImplementedError + + def ping(self): + """ + Use the 'watchdog' request to ping the FPGA host. + :return: True or False + """ + raise NotImplementedError + + def disconnect(self): + """ + + :return: + """ + raise NotImplementedError + + def read(self, device_name, size, offset=0): + """ + + :param device_name: + :param size: + :param offset: + :return: + """ + raise NotImplementedError + + def blindwrite(self, device_name, data, offset=0): + """ + + :param device_name: + :param data: + :param offset: + :return: + """ + raise NotImplementedError + + def listdev(self): + """ + Get a list of the memory bus items in this design. + :return: a list of memory devices + """ + raise NotImplementedError + + def deprogram(self): + """ + Deprogram the FPGA connected by this transport + :return: + """ + raise NotImplementedError + + def set_igmp_version(self, version): + """ + :param version + :return: + """ + raise NotImplementedError + + def upload_to_ram_and_program(self, filename, port=-1, timeout=10, + wait_complete=True): + """ + Upload an FPG file to RAM and then program the FPGA. + :param filename: the file to upload + :param port: the port to use on the rx end, -1 means a random port + :param timeout: how long to wait, seconds + :param wait_complete: wait for the transaction to complete, return + after upload if False + :return: + """ + raise NotImplementedError + + def upload_to_flash(self, binary_file, port=-1, force_upload=False, + timeout=30, wait_complete=True): + """ + Upload the provided binary file to the flash filesystem. + :param binary_file: filename of the binary file to upload + :param port: host-side port, -1 means a random port will be used + :param force_upload: upload the binary even if it already exists + on the host + :param timeout: upload timeout, in seconds + :param wait_complete: wait for the upload to complete, or just + kick it off + :return: + """ + raise NotImplementedError + + def get_system_information(self, filename=None, fpg_info=None): + """ + + :param filename: + :param fpg_info: + :return: processed_filename, processed_fpg_info + """ + raise NotImplementedError + + def post_get_system_information(self): + """ + Cleanup run after get_system_information + :return: + """ + raise NotImplementedError + + +# end diff --git a/src/katcp_fpga.py b/src/transport_katcp.py similarity index 90% rename from src/katcp_fpga.py rename to src/transport_katcp.py index 3681c7c7..0ba189e6 100644 --- a/src/katcp_fpga.py +++ b/src/transport_katcp.py @@ -8,7 +8,7 @@ import socket -from casperfpga import CasperFpga +from transport import Transport from utils import parse_fpg, create_meta_dictionary LOGGER = logging.getLogger(__name__) @@ -64,8 +64,10 @@ def sendfile(filename, targethost, port, result_queue, timeout=2): (targethost, time.time())) -class KatcpFpga(CasperFpga, katcp.CallbackClient): - +class KatcpTransport(Transport, katcp.CallbackClient): + """ + A katcp transport client for a casperfpga object + """ def __init__(self, host, port=7147, timeout=20.0, connect=True): """ @@ -78,9 +80,7 @@ def __init__(self, host, port=7147, timeout=20.0, connect=True): katcp.CallbackClient.__init__(self, host, port, tb_limit=20, timeout=timeout, logger=LOGGER, auto_reconnect=True) - CasperFpga.__init__(self, host) - self.system_info = {'last_programmed': '', - 'system_name': None, 'program_filename': ''} + Transport.__init__(self, host) self.unhandled_inform_handler = None self._timeout = timeout if connect: @@ -142,7 +142,6 @@ def disconnect(self): Disconnect from the device server. :return: """ - super(KatcpFpga, self).stop() self.join(timeout=self._timeout) LOGGER.info('%s: disconnected' % self.host) @@ -187,7 +186,6 @@ def listdev(self, getsize=False, getaddress=False): :return: a list of memory devices """ if getsize: - _, informs = self.katcprequest(name='listdev', request_timeout=self._timeout, request_args=('size',)) @@ -242,10 +240,7 @@ def is_running(self): reply, _ = self.katcprequest(name='fpgastatus', request_timeout=self._timeout, require_ok=False) - if reply.arguments[0] == 'ok': - return True - else: - return False + return reply.arguments[0] == 'ok' def read(self, device_name, size, offset=0): """ @@ -280,28 +275,28 @@ def bulkread(self, device_name, size, offset=0): """ Return size_bytes of binary data with carriage-return escape-sequenced. Uses much faster bulkread katcp command which returns data in pages - using informs rather than one read reply, which has significant buffering - overhead on the ROACH. + using informs rather than one read reply, which has significant + buffering overhead on the ROACH. :param device_name: name of the memory device from which to read :param size: how many bytes to read :param offset: the offset at which to read :return: binary data string """ - _, informs = self.katcprequest(name='bulkread', - request_timeout=self._timeout, - require_ok=True, - request_args=(device_name, str(offset), - str(size))) + _, informs = self.katcprequest( + name='bulkread', request_timeout=self._timeout, require_ok=True, + request_args=(device_name, str(offset), str(size))) return ''.join([i.arguments[0] for i in informs]) def program(self, filename=None): """ Program the FPGA with the specified binary file. - :param filename: name of file to program, can vary depending on the formats - supported by the device. e.g. fpg, bof, bin + :param filename: name of file to program, can vary depending on + the formats supported by the device. e.g. fpg, bof, bin :return: """ - # TODO - The logic here is for broken TCPBORPHSERVER - needs to be fixed. + raise DeprecationWarning('This does not seem to be used anymore.' + 'Use upload_to_ram_and_program') + # TODO - The logic here is for broken TCPBORPHSERVER, needs fixing if 'program_filename' in self.system_info.keys(): if filename is None: filename = self.system_info['program_filename'] @@ -316,9 +311,7 @@ def program(self, filename=None): 'Exiting.' % self.host) raise RuntimeError('Cannot program with no filename given. ' 'Exiting.') - unhandled_informs = [] - # set the unhandled informs callback self.unhandled_inform_handler = \ lambda msg: unhandled_informs.append(msg) @@ -370,7 +363,6 @@ def deprogram(self): # tap devices time.sleep(0.05) reply, _ = self.katcprequest(name='progdev', require_ok=True) - super(KatcpFpga, self).deprogram() LOGGER.info('%s: deprogrammed okay' % self.host) except KatcpRequestError as exc: LOGGER.exception('{}: could not deprogram FPGA, katcp request ' @@ -379,17 +371,22 @@ def deprogram(self): 'FPGA - {}'.format(self.host, exc)) def _unsubscribe_all_taps(self): + """ + Remove all multicast subscriptions before deprogramming the ROACH + :return: + """ reply, informs = self.katcprequest(name='tap-info', require_ok=True) taps = [inform.arguments[0] for inform in informs] for tap in taps: reply, _ = self.katcprequest(name='tap-multicast-remove', - request_args=(tap,)) + request_args=(tap,)) if not reply.reply_ok(): LOGGER.warn('{}: could not unsubscribe tap {} from multicast ' 'groups on FPGA'.format(self.host, tap)) def set_igmp_version(self, version): - """Sets version of IGMP multicast protocol to use + """ + Sets version of IGMP multicast protocol to use :param version: IGMP protocol version, 0 for kernel default, 1, 2 or 3 Note: won't work if config keep file is present since the @@ -410,19 +407,17 @@ def upload_to_ram_and_program(self, filename, port=-1, timeout=10, after upload if False :return: """ - LOGGER.info('%s: uploading %s, programming when done' % - (self.host, filename)) - + LOGGER.info('%s: uploading %s, programming when done' % ( + self.host, filename)) # does the file that is to be uploaded exist on the local filesystem? os.path.getsize(filename) # function to make the request to the KATCP server def makerequest(result_queue): try: - result = self.katcprequest(name='progremote', - request_timeout=timeout, - require_ok=True, - request_args=(port, )) + result = self.katcprequest( + name='progremote', request_timeout=timeout, + require_ok=True, request_args=(port, )) if result[0].arguments[0] == katcp.Message.OK: result_queue.put('') else: @@ -435,7 +430,6 @@ def makerequest(result_queue): if port == -1: port = random.randint(2000, 2500) - # start the request thread and join request_queue = Queue.Queue() request_thread = threading.Thread(target=makerequest, @@ -448,7 +442,6 @@ def makerequest(result_queue): if request_result != '': raise RuntimeError('progremote request(%s) on host %s failed' % (request_result, self.host)) - # start the upload thread and join upload_queue = Queue.Queue() unhandled_informs_queue = Queue.Queue() @@ -460,7 +453,7 @@ def makerequest(result_queue): if not wait_complete: self.unhandled_inform_handler = None self._timeout = old_timeout - return + return True upload_thread.join() # wait for the file upload to complete upload_result = upload_queue.get() if upload_result != '': @@ -479,9 +472,8 @@ def makerequest(result_queue): LOGGER.info('%s: programming done.' % self.host) self.unhandled_inform_handler = None self._timeout = old_timeout - self.system_info['last_programmed'] = filename - self.get_system_information() - return + self.prog_info['last_programmed'] = filename + return True def upload_to_flash(self, binary_file, port=-1, force_upload=False, timeout=30, wait_complete=True): @@ -560,10 +552,9 @@ def _delete_bof(self, filename): else: bofs = [filename] for bof in bofs: - result = self.katcprequest(name='delbof', - request_timeout=self._timeout, - require_ok=True, - request_args=(bof, )) + result = self.katcprequest( + name='delbof', request_timeout=self._timeout, + require_ok=True, request_args=(bof, )) if result[0].arguments[0] != katcp.Message.OK: raise RuntimeError('Failed to delete bof file %s' % bof) @@ -572,13 +563,13 @@ def tap_arp_reload(self): Have the tap driver reload its ARP table right now. :return: """ - reply, _ = self.katcprequest(name='tap-arp-reload', request_timeout=-1, - require_ok=True) + reply, _ = self.katcprequest( + name='tap-arp-reload', request_timeout=-1, require_ok=True) if reply.arguments[0] != 'ok': raise RuntimeError('%s: failure requesting ARP reload.' % self.host) def __str__(self): - return 'KatcpFpga(%s):%i - %s' % \ + return 'KatcpTransport(%s):%i - %s' % \ (self.host, self._bindaddr[1], 'connected' if self.is_connected() else 'disconnected') @@ -611,14 +602,12 @@ def _read_design_info_from_host(self, device=None): """ LOGGER.debug('%s: reading designinfo' % self.host) if device is None: - reply, informs = self.katcprequest(name='meta', - request_timeout=10.0, - require_ok=True) + reply, informs = self.katcprequest( + name='meta', request_timeout=10.0, require_ok=True) else: - reply, informs = self.katcprequest(name='meta', - request_timeout=10.0, - require_ok=True, - request_args=(device, )) + reply, informs = self.katcprequest( + name='meta', request_timeout=10.0, require_ok=True, + request_args=(device, )) if reply.arguments[0] != 'ok': raise RuntimeError('Could not read meta information ' 'from %s' % self.host) @@ -666,8 +655,9 @@ def _read_coreinfo_from_host(self): if addrdev == byte_dev: byte_size = int(byte_size.split(':')[0]) address = int(address.split(':')[0], 16) - memorymap_dict[byte_dev] = {'address': address, - 'bytes': byte_size} + memorymap_dict[byte_dev] = { + 'address': address, 'bytes': byte_size + } matched = True continue if not matched: @@ -692,19 +682,30 @@ def get_system_information(self, filename=None, fpg_info=None): else: device_dict = self._read_design_info_from_host() memorymap_dict = self._read_coreinfo_from_host() - super(KatcpFpga, self).get_system_information( - fpg_info=(device_dict, memorymap_dict)) + return None, (device_dict, memorymap_dict) + + def post_get_system_information(self): + """ + Cleanup run after get_system_information + :return: + """ + pass def unhandled_inform(self, msg): """ Overloaded from CallbackClient - What do we do with unhandled KATCP inform messages that this device receives? + What do we do with unhandled KATCP inform messages that + this device receives? Pass it onto the registered function, if it's not None """ if self.unhandled_inform_handler is not None: self.unhandled_inform_handler(msg) def check_phy_counter(self): + """ + + :return: + """ request_args = ((0, 0), (0, 1), (1, 0), (1, 1)) for arg in request_args: result0 = self.katcprequest('phywatch', request_args=arg) @@ -715,6 +716,8 @@ def check_phy_counter(self): LOGGER.info('%s: check_phy_counter - TRUE.' % self.host) return True else: - LOGGER.error('%s: check_phy_counter failed on PHY %s - FALSE.' % (self.host, arg)) + LOGGER.error('%s: check_phy_counter failed on PHY %s - ' + 'FALSE.' % (self.host, arg)) return False + # end diff --git a/src/skarab_fpga.py b/src/transport_skarab.py similarity index 87% rename from src/skarab_fpga.py rename to src/transport_skarab.py index ecc85454..e7aecdbe 100644 --- a/src/skarab_fpga.py +++ b/src/transport_skarab.py @@ -9,9 +9,8 @@ import hashlib import skarab_definitions as sd -from casperfpga import CasperFpga, tengbe as caspertengbe -from utils import parse_fpg -import tengbe +from transport import Transport +from fortygbe import FortyGbe __author__ = 'tyronevb' __date__ = 'April 2016' @@ -55,206 +54,9 @@ class UnknownDeviceError(ValueError): pass -class FortyGbe(object): +class SkarabTransport(Transport): """ - - """ - def __init__(self, parent, position, base_addr=0x50000): - """ - - :param parent: - :param position: - """ - self.parent = parent - self.position = position - self.base_addr = base_addr - - def _wbone_rd(self, addr): - """ - - :param addr: - :return: - """ - return self.parent.read_wishbone(addr) - - def _wbone_wr(self, addr, val): - """ - - :param addr: - :param val: - :return: - """ - return self.parent.write_wishbone(addr, val) - - def enable(self): - """ - - :return: - """ - en_port = self._wbone_rd(self.base_addr + 0x20) - if en_port >> 16 == 1: - return - en_port_new = (1 << 16) + (en_port & (2 ** 16 - 1)) - self._wbone_wr(self.base_addr + 0x20, en_port_new) - if self._wbone_rd(self.base_addr + 0x20) != en_port_new: - errmsg = 'Error enabling 40gbe port' - LOGGER.error(errmsg) - raise ValueError(errmsg) - - def disable(self): - """ - - :return: - """ - en_port = self._wbone_rd(self.base_addr + 0x20) - if en_port >> 16 == 0: - return - old_port = en_port & (2 ** 16 - 1) - self._wbone_wr(self.base_addr + 0x20, old_port) - if self._wbone_rd(self.base_addr + 0x20) != old_port: - errmsg = 'Error disabling 40gbe port' - LOGGER.error(errmsg) - raise ValueError(errmsg) - - def get_ip(self): - """ - - :return: - """ - ip = self._wbone_rd(self.base_addr + 0x10) - return caspertengbe.IpAddress(ip) - - def get_port(self): - """ - - :return: - """ - en_port = self._wbone_rd(self.base_addr + 0x20) - return en_port & (2 ** 16 - 1) - - def set_port(self, port): - """ - - :param port: - :return: - """ - en_port = self._wbone_rd(self.base_addr + 0x20) - if en_port & (2 ** 16 - 1) == port: - return - en_port_new = ((en_port >> 16) << 16) + port - self._wbone_wr(self.base_addr + 0x20, en_port_new) - if self._wbone_rd(self.base_addr + 0x20) != en_port_new: - errmsg = 'Error setting 40gbe port to 0x%04x' % port - LOGGER.error(errmsg) - raise ValueError(errmsg) - - def details(self): - """ - Get the details of the ethernet mac on this device - :return: - """ - from tengbe import IpAddress, Mac - gbebase = self.base_addr - gbedata = [] - for ctr in range(0, 0x40, 4): - gbedata.append(self._wbone_rd(gbebase + ctr)) - gbebytes = [] - for d in gbedata: - gbebytes.append((d >> 24) & 0xff) - gbebytes.append((d >> 16) & 0xff) - gbebytes.append((d >> 8) & 0xff) - gbebytes.append((d >> 0) & 0xff) - pd = gbebytes - returnval = { - 'ip_prefix': '%i.%i.%i.' % (pd[0x10], pd[0x11], pd[0x12]), - 'ip': IpAddress('%i.%i.%i.%i' % (pd[0x10], pd[0x11], - pd[0x12], pd[0x13])), - 'mac': Mac('%i:%i:%i:%i:%i:%i' % (pd[0x02], pd[0x03], pd[0x04], - pd[0x05], pd[0x06], pd[0x07])), - 'gateway_ip': IpAddress('%i.%i.%i.%i' % (pd[0x0c], pd[0x0d], - pd[0x0e], pd[0x0f])), - 'fabric_port': ((pd[0x22] << 8) + (pd[0x23])), - 'fabric_en': bool(pd[0x21] & 1), - 'xaui_lane_sync': [ - bool(pd[0x27] & 4), bool(pd[0x27] & 8), - bool(pd[0x27] & 16), bool(pd[0x27] & 32)], - 'xaui_status': [ - pd[0x24], pd[0x25], pd[0x26], pd[0x27]], - 'xaui_chan_bond': bool(pd[0x27] & 64), - 'xaui_phy': { - 'rx_eq_mix': pd[0x28], - 'rx_eq_pol': pd[0x29], - 'tx_preemph': pd[0x2a], - 'tx_swing': pd[0x2b]}, - 'multicast': { - 'base_ip': IpAddress('%i.%i.%i.%i' % (pd[0x30], pd[0x31], - pd[0x32], pd[0x33])), - 'ip_mask': IpAddress('%i.%i.%i.%i' % (pd[0x34], pd[0x35], - pd[0x36], pd[0x37])), - 'subnet_mask': IpAddress('%i.%i.%i.%i' % (pd[0x38], pd[0x39], - pd[0x3a], pd[0x3b]))} - } - possible_addresses = [int(returnval['multicast']['base_ip'])] - mask_int = int(returnval['multicast']['ip_mask']) - for ctr in range(32): - mask_bit = (mask_int >> ctr) & 1 - if not mask_bit: - new_ips = [] - for ip in possible_addresses: - new_ips.append(ip & (~(1 << ctr))) - new_ips.append(new_ips[-1] | (1 << ctr)) - possible_addresses.extend(new_ips) - returnval['multicast']['rx_ips'] = [] - tmp = list(set(possible_addresses)) - for ip in tmp: - returnval['multicast']['rx_ips'].append(IpAddress(ip)) - return returnval - - def multicast_receive(self, ip_str, group_size): - """ - Send a request to KATCP to have this tap instance send a multicast - group join request. - :param ip_str: A dotted decimal string representation of the base - mcast IP address. - :param group_size: An integer for how many mcast addresses from - base to respond to. - :return: - """ - - ip = tengbe.IpAddress('239.2.0.64') - ip_high = ip.ip_int >> 16 - ip_low = ip.ip_int & 65535 - - mask = tengbe.IpAddress('255.255.255.240') - mask_high = mask.ip_int >> 16 - mask_low = mask.ip_int & 65535 - - request = sd.ConfigureMulticastReq( - self.parent.seq_num, 1, ip_high, ip_low, mask_high, mask_low) - - resp = self.parent.send_packet( - payload=request.create_payload(), - response_type='ConfigureMulticastResp', - expect_response=True, - command_id=sd.MULTICAST_REQUEST, - number_of_words=11, pad_words=4) - - resp_ip = tengbe.IpAddress( - resp.fabric_multicast_ip_address_high << 16 | - resp.fabric_multicast_ip_address_low) - - resp_mask = tengbe.IpAddress( - resp.fabric_multicast_ip_address_mask_high << 16 | - resp.fabric_multicast_ip_address_mask_low) - - LOGGER.info('Multicast Configured') - LOGGER.info('Multicast address: {}'.format(resp_ip.ip_str)) - LOGGER.info('Multicast mask: {}'.format(resp_mask.ip_str)) - - -class SkarabFpga(CasperFpga): - """ - + The network transport for a SKARAB-type interface. """ # create dictionary of skarab_definitions module sd_dict = vars(sd) @@ -265,22 +67,19 @@ def __init__(self, host): :param host: IP Address of the targeted SKARAB Board :return: none """ - super(SkarabFpga, self).__init__(host) - - self.skarab_ip_address = host + Transport.__init__(self, host) # sequence number for control packets self._seq_num = 0 # initialize UDP socket for ethernet control packets - self.skarab_ctrl_sock = socket.socket( - socket.AF_INET, socket.SOCK_DGRAM) + self.skarab_ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # prevent socket from blocking self.skarab_ctrl_sock.setblocking(0) # create tuple for ethernet control packet address self.skarab_eth_ctrl_port = ( - self.skarab_ip_address, sd.ETHERNET_CONTROL_PORT_ADDRESS) + self.host, sd.ETHERNET_CONTROL_PORT_ADDRESS) # initialize UDP socket for fabric packets self.skarab_fpga_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -289,32 +88,33 @@ def __init__(self, host): self.skarab_ctrl_sock.setblocking(0) # create tuple for fabric packet address - self.skarab_fpga_port = ( - self.skarab_ip_address, sd.ETHERNET_FABRIC_PORT_ADDRESS) + self.skarab_fpga_port = (self.host, sd.ETHERNET_FABRIC_PORT_ADDRESS) # flag for keeping track of SDRAM state - self.__sdram_programmed = False - - # dict for programming/uploading info - self.prog_info = {'last_uploaded': '', 'last_programmed': ''} + self._sdram_programmed = False # dict for sensor data, empty at initialization self.sensor_data = {} - self.gbes = [] - self.gbes.append(FortyGbe(self, 0)) - self.gbes.append(FortyGbe(self, 0, 0x50000-16384)) - # check if connected to host if self.is_connected(): LOGGER.info( - '%s: port(%s) created%s.' % ( - self.skarab_ip_address, sd.ETHERNET_CONTROL_PORT_ADDRESS, - ' & connected')) + '%s: port(%s) created %s.' % ( + self.host, sd.ETHERNET_CONTROL_PORT_ADDRESS, '& connected')) else: - LOGGER.info( - 'Error connecting to %s: port%s' % ( - self.skarab_ip_address, sd.ETHERNET_CONTROL_PORT_ADDRESS)) + LOGGER.info('Error connecting to %s: port%s' % ( + self.host, sd.ETHERNET_CONTROL_PORT_ADDRESS)) + + # TODO - add the one_gbe + # self.gbes = [] + # self.gbes.append(FortyGbe(self, 0)) + # # self.gbes.append(FortyGbe(self, 0, 0x50000 - 0x4000)) + + def connect(self, timeout=None): + pass + + def disconnect(self): + pass def is_connected(self, retries=3): """ @@ -325,6 +125,21 @@ def is_connected(self, retries=3): data = self.read_board_reg(sd.C_RD_VERSION_ADDR, retries=retries) return True if data else False + def is_running(self): + """ + + :return: + """ + return self.is_connected() + + def set_igmp_version(self, version): + """ + Compatibility function to match KatcpFpga interface. + :param version: + :return: + """ + pass + def loopbacktest(self, iface): """ Run the loopback test. @@ -350,7 +165,7 @@ def _get_device_address(self, device_name): # map device name to address, if can't find, bail if device_name in self.memory_devices: return self.memory_devices[device_name].address - elif type(device_name) == int and 0 <= device_name < 2 ** 32: + elif (type(device_name) == int) and (0 <= device_name < 2 ** 32): # also support absolute address values LOGGER.warning('Absolute address given: 0x%06x' % device_name) return device_name @@ -378,8 +193,9 @@ def read(self, device_name, size, offset=0, use_bulk=True): num_bytes_corrected = size + offset_diff num_reads = int(math.ceil(num_bytes_corrected / 4.0)) addr_start = addr + offset - offset_diff - # LOGGER.info('size(%i) offset(%i) addr(0x%06x) => offset_corrected(%i) ' - # 'size_corrected(%i) addr_start(0x%06x) numreads(%i)' % ( + # LOGGER.info('size(%i) offset(%i) addr(0x%06x) => ' + # 'offset_corrected(%i) size_corrected(%i) ' + # 'addr_start(0x%06x) numreads(%i)' % ( # size, offset, addr, offset_bytes, num_bytes_corrected, # addr_start, num_reads)) # address to read is starting address plus offset @@ -520,24 +336,21 @@ def blindwrite(self, device_name, data, offset=0): :param offset: the offset, in bytes, at which to write :return: """ - # map device name to address, if can't find, bail - if device_name in self.memory_devices.keys(): - addr = self.memory_devices[device_name].address - else: - LOGGER.error('Unknown device name') - raise KeyError - + addr = self._get_device_address(device_name) + # # map device name to address, if can't find, bail + # if device_name in self.memory_devices.keys(): + # addr = self.memory_devices[device_name].address + # else: + # LOGGER.error('Unknown device name') + # raise KeyError assert (type(data) == str), 'Must supply binary packed string data' assert (len(data) % 4 == 0), 'Must write 32-bit-bounded words' assert (offset % 4 == 0), 'Must write 32-bit-bounded words' - # split the data into two 16-bit words data_high = data[:2] data_low = data[2:] - addr += offset addr_high, addr_low = self.data_split_and_pack(addr) - # create payload packet structure for write request request = sd.WriteWishboneReq(self.seq_num, addr_high, addr_low, data_high, data_low) @@ -553,37 +366,10 @@ def deprogram(self): This actually reboots & boots from the Golden Image :return: nothing """ - # trigger reboot of FPGA self.reboot_fpga() - - # call the parent method to reset device info - super(SkarabFpga, self).deprogram() LOGGER.info('%s: deprogrammed okay' % self.host) - def is_running(self): - """ - Is the FPGA programmed and running? - :return: True or False - """ - raise NotImplementedError - - def listdev(self): - """ - Get a list of the memory bus items in this design. - :return: a list of memory devices - """ - return self.memory_devices.keys() - - def upload_to_flash(self, filename): - """ - Upload an FPG file to flash memory. - This is used to perform in-field upgrades of the SKARAB - :param filename: the file to upload - :return: - """ - raise NotImplementedError - def program_from_flash(self): """ Program the FPGA from flash memory. @@ -599,7 +385,7 @@ def boot_from_sdram(self): :return: """ # check if sdram was programmed prior - if not self.__sdram_programmed: + if not self._sdram_programmed: errmsg = 'SDRAM not programmed.' LOGGER.error(errmsg) raise SkarabSdramError(errmsg) @@ -607,19 +393,7 @@ def boot_from_sdram(self): self._complete_sdram_configuration() LOGGER.info('Booting from SDRAM.') # clear sdram programmed flag - self.__sdram_programmed = False - # if fpg file used, get design information - if os.path.splitext(self.prog_info['last_uploaded'])[1] == '.fpg': - super(SkarabFpga, self).get_system_information( - filename=self.prog_info['last_uploaded']) - self.__create_memory_map() - else: - # if not fpg file, then - # raise NotImplementedError - # self._CasperFpga__reset_device_info() - infomsg = 'Now allowing for programming of .bin files et al' - LOGGER.info(infomsg) - + self._sdram_programmed = False # update programming info self.prog_info['last_programmed'] = self.prog_info['last_uploaded'] self.prog_info['last_uploaded'] = '' @@ -655,17 +429,17 @@ def upload_to_ram(self, filename, verify=False, check_pkt_count=False): image_to_program = self.convert_bit_to_bin(filename) elif file_extension == '.bin': LOGGER.info('Reading .bin file.') - image_to_program = open(filename, 'rb').read() - if not self.check_bitstream(image_to_program): - LOGGER.info('Incompatible .bin file. Attemping to convert.') - image_to_program = self.reorder_bytes_in_bin_file( - image_to_program) + (result, image_to_program) = self.check_bitstream( + open(filename, 'rb').read()) + if not result: + LOGGER.info('Incompatible .bin file.') else: raise TypeError('Invalid file type. Only use .fpg, .bit, ' '.hex or .bin files') # check the generated bitstream - if not self.check_bitstream(image_to_program): + (result, image_to_program) = self.check_bitstream(image_to_program) + if not result: errmsg = 'Incompatible image file. Cannot program SKARAB.' LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) @@ -677,26 +451,20 @@ def upload_to_ram(self, filename, verify=False, check_pkt_count=False): self._prepare_sdram_ram_for_programming() if file_extension == '.fpg': - - # As per Wes, allowing for backwards compatibility - # - Check if the md5sums exist, i.e. only need to check if the md5_bitstream key exists - self.get_system_information(filename) # This will hopefully populate fpga.system_info - meta_data_dict = self.system_info - - if 'md5_bitstream' in meta_data_dict.keys(): + (md5_header, md5_bitstream) = self.extract_md5_from_fpg(filename) + if md5_header is not None and md5_bitstream is not None: # Calculate and compare MD5 sums here, before carrying on - fpgfile_md5sum = meta_data_dict['md5_bitstream'] # system_info is a dictionary + # system_info is a dictionary bitstream_md5sum = hashlib.md5(image_to_program).hexdigest() - - if bitstream_md5sum != fpgfile_md5sum: - # Problem + if bitstream_md5sum != md5_bitstream: errmsg = "bitstream_md5sum != fpgfile_md5sum" LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) else: # .fpg file was created using an older version of mlib_devel - errmsg = "An older version of mlib_devel generated " + filename + "." \ - " Please update to include the md5sum on the bitstream in the .fpg header." + errmsg = 'An older version of mlib_devel generated ' + \ + filename + '. Please update to include the md5sum ' \ + 'on the bitstream in the .fpg header.' LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) @@ -831,20 +599,20 @@ def upload_to_ram(self, filename, verify=False, check_pkt_count=False): else: LOGGER.info('%s uploaded to RAM without being verified.' % filename) - self.__sdram_programmed = True + self._sdram_programmed = True self.prog_info['last_uploaded'] = filename def upload_to_ram_and_program(self, filename, port=-1, timeout=60, wait_complete=True, attempts=2): attempt_ctr = 0 while attempt_ctr < attempts: - res = self._upload_to_ram_and_program(filename, port, timeout, - wait_complete) + res = self._upload_to_ram_and_program( + filename, port, timeout, wait_complete) + attempt_ctr += 1 if res: return True - raise ProgrammingError('Gave up programming after %i attempt%s' - '' % (attempts, - 's' if attempts > 1 else '')) + raise ProgrammingError('Gave up programming after %i attempt%s' % ( + attempts, 's' if attempts > 1 else '')) def _upload_to_ram_and_program(self, filename, port=-1, timeout=60, wait_complete=True): @@ -858,17 +626,15 @@ def _upload_to_ram_and_program(self, filename, port=-1, timeout=60, and hex files) :return: True, if success """ - - # set the port back to fabric programming - if self.gbes[0].get_port() != sd.ETHERNET_FABRIC_PORT_ADDRESS: - LOGGER.info('Resetting 40gbe port to 0x%04x' % - sd.ETHERNET_FABRIC_PORT_ADDRESS) - self.gbes[0].set_port(sd.ETHERNET_FABRIC_PORT_ADDRESS) # set the port back to fabric programming - if self.gbes[1].get_port() != sd.ETHERNET_FABRIC_PORT_ADDRESS: - LOGGER.info('Resetting 1gbe port to 0x%04x' % - sd.ETHERNET_FABRIC_PORT_ADDRESS) - self.gbes[1].set_port(sd.ETHERNET_FABRIC_PORT_ADDRESS) + need_sleep = False + for gbe in self.gbes: + if gbe.get_port() != sd.ETHERNET_FABRIC_PORT_ADDRESS: + LOGGER.info('Resetting %s to 0x%04x' % ( + gbe.name, sd.ETHERNET_FABRIC_PORT_ADDRESS)) + gbe.set_port(sd.ETHERNET_FABRIC_PORT_ADDRESS) + need_sleep = True + if need_sleep: time.sleep(1) # put the interface into programming mode @@ -951,7 +717,7 @@ def clear_sdram(self): self.sdram_reconfigure(clear_sdram=True, clear_eth_stats=True) # clear sdram programmed flag - self.__sdram_programmed = False + self._sdram_programmed = False # clear prog_info for last uploaded self.prog_info['last_uploaded'] = '' @@ -981,15 +747,25 @@ def verify_sdram_contents(self, filename): LOGGER.info('.bit file detected. Converting to .bin.') file_contents = self.convert_bit_to_bin(filename) elif file_extension == '.bin': - file_contents = open(filename, 'rb').read() - if not self.check_bitstream(file_contents): - LOGGER.info('Incompatible .bin file. Attemping to convert.') - file_contents = self.reorder_bytes_in_bin_file( - file_contents) + LOGGER.info('Reading .bin file.') + (result, image_to_program) = self.check_bitstream( + open(filename, 'rb').read()) + if not result: + LOGGER.info('Incompatible .bin file.') else: raise TypeError('Invalid file type. Only use .fpg, .bit, ' '.hex or .bin files') + # check the generated bitstream + (result, image_to_program) = self.check_bitstream(image_to_program) + if not result: + errmsg = 'Incompatible image file. Cannot program SKARAB.' + LOGGER.error(errmsg) + raise InvalidSkarabBitstream(errmsg) + LOGGER.info('Valid bitstream detected.') + + # at this point the bitstream is in memory + # prep SDRAM for reading self.sdram_reconfigure(output_mode=sd.SDRAM_READ_MODE, reset_sdram_read_addr=True, enable_debug=True) @@ -1124,7 +900,7 @@ def unpack_payload(response_payload, response_type, number_of_words, unpacked_data[5:389] = [read_bytes] # return response from skarab - return SkarabFpga.sd_dict[response_type](*unpacked_data) + return SkarabTransport.sd_dict[response_type](*unpacked_data) @property def seq_num(self): @@ -1221,7 +997,7 @@ def send_packet(self, payload, response_type, LOGGER.info('Cleared recv buffer.') raise KeyboardInterrupt retransmit_count += 1 - errmsg = 'Socket timeout. Response packet not received.' + errmsg = '%s: socket timeout. Response packet not received.' % self.host LOGGER.error(errmsg) raise SkarabSendPacketError(errmsg) @@ -1251,7 +1027,7 @@ def reboot_fpga(self): # reset sequence numbers self._seq_num = 0 # reset the sdram programmed flag - self.__sdram_programmed = False + self._sdram_programmed = False # clear prog_info self.prog_info['last_programmed'] = '' self.prog_info['last_uploaded'] = '' @@ -1689,16 +1465,16 @@ def read_flash_words(self, flash_address, num_words=256): LOGGER.error(errmsg) raise InvalidResponse(errmsg) - def verify_words(self, filename, flash_address=sd.DEFAULT_START_ADDRESS): + def verify_words(self, bitstream, flash_address=sd.DEFAULT_START_ADDRESS): ''' This method reads back the programmed words from the flash device and checks it against the data in the input .bin file uploaded to the Flash Memory. - :param filename: Of the input .bin file that was programmed to Flash Memory + :param bitstream: Of the input .bin file that was programmed to Flash Memory :param flash_address: 32-bit Address in the NOR flash to START reading from :return: Boolean success/fail ''' - bitstream = open(filename, 'rb').read() + # bitstream = open(filename, 'rb').read() bitstream_chunks = [bitstream[i:i+512] for i in range(0, len(bitstream), 512)] # Now we have 512-byte chunks # Using 512-byte chunks = 256-word chunks because we are reading 256 words at a time from the Flash Memory @@ -1827,18 +1603,18 @@ def program_flash_words(self, flash_address, total_num_words, num_words, do_buff LOGGER.error(errmsg) raise InvalidResponse(errmsg) - def program_words(self, filename, flash_address=sd.DEFAULT_START_ADDRESS): + def program_words(self, bitstream, flash_address=sd.DEFAULT_START_ADDRESS): ''' Higher level function call to Program n-many words from an input .hex (eventually .bin) file This method scrolls through the words in the bitstream, and packs them into 256+256 words This method erases the required number of blocks in the flash - Only the required number of flash blocks are erased - :param filename: Of the input .bin file to write to Flash Memory + :param bitstream: Of the input .bin file to write to Flash Memory :param flash_address: Address in Flash Memory from where to start programming :return: Boolean Success/Fail - 1/0 ''' - bitstream = open(filename, 'rb').read() + # bitstream = open(filename, 'rb').read() # As per upload_to_ram() except now we're programming in chunks of 512 words size = len(bitstream) # Split image into chunks of 512 words = 1024 bytes @@ -1991,7 +1767,7 @@ def analyse_file(filename): def virtex_flash_reconfig(self, filename, flash_address=sd.DEFAULT_START_ADDRESS, blind_reconfig=False): ''' This is the entire function that makes the necessary calls to reconfigure the - :param filename: The actual .hex file that is to be written to the Virtex FPGA + :param filename: The actual .bin file that is to be written to the Virtex FPGA :param flash_address: 32-bit Address in the NOR flash to start programming from :param blind_reconfig: Reconfigure the board and don't wait to Verify what has been written :return: Success/Fail - 0/1 @@ -2000,19 +1776,34 @@ def virtex_flash_reconfig(self, filename, flash_address=sd.DEFAULT_START_ADDRESS # For completeness, make sure the input file is of a .bin disposition file_extension = os.path.splitext(filename)[1] - if file_extension != '.bin': - # Problem - errmsg = "Input file was not a .bin file" + # if file_extension != '.bin': + # # Problem + # errmsg = "Input file was not a .bin file" + # LOGGER.error(errmsg) + # raise InvalidSkarabBitstream(errmsg) + # else: Continue + + if file_extension == '.hex': + LOGGER.info('.hex detected. Converting to .bin.') + image_to_program = self.convert_hex_to_bin(filename) + elif file_extension == '.bin': + LOGGER.info('.bin file detected.') + image_to_program = open(filename, 'rb').read() + else: + # File extension was neither .hex nor .bin + errmsg = 'Please use .hex or .bin file to reconfigure Flash Memory' LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) - # else: Continue - if not self.check_bitstream(open(filename, 'rb').read()): - errmsg = "Incompatible .bin file detected. Cannot Program Flash Memory." + (result, image_to_program) = self.check_bitstream(image_to_program) + if not result: + errmsg = "Incompatible .bin file detected." LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) LOGGER.debug("VIRTEX FLASH RECONFIG: Analysing Words") + # Can still analyse the filename, as the file size should still be the same + # Regardless of swapping the endianness num_words, num_memory_blocks = self.analyse_file(filename) if (num_words == 0) or (num_memory_blocks == 0): @@ -2031,7 +1822,7 @@ def virtex_flash_reconfig(self, filename, flash_address=sd.DEFAULT_START_ADDRESS # else: Continue LOGGER.debug("VIRTEX FLASH RECONFIG: Programming Words to Flash Memory") - if not self.program_words(filename, flash_address): + if not self.program_words(image_to_program, flash_address): # Problem errmsg = "Failed to Program Flash Memory Blocks" LOGGER.error(errmsg) @@ -2040,7 +1831,7 @@ def virtex_flash_reconfig(self, filename, flash_address=sd.DEFAULT_START_ADDRESS if not blind_reconfig: LOGGER.debug("VIRTEX FLASH RECONFIG: Verifying words that were written to Flash Memory") - if not self.verify_words(filename): + if not self.verify_words(image_to_program, flash_address): # Problem errmsg = "Failed to Program Flash Memory Blocks" LOGGER.error(errmsg) @@ -2715,35 +2506,44 @@ def extract_bitstream(filename, extract_to_disk=False): return bitstream - @staticmethod - def check_bitstream(bitstream): + def check_bitstream(self, bitstream): """ Checks the bitstream to see if it is valid. i.e. if it contains a known, correct substring in its header - :param bitstream: bitstream to check - :return: True or False + If bitstream endianness is incorrect, byte-swap data and return altered bitstream + :param bitstream: Of the input (.bin) file to be checked + :return: tuple - (True/False, bitstream) """ # check if filename or bitstream: - #if '.bin' in bitstream: + # if '.bin' in bitstream: # # filename given - # bitstream = open(bitstream, 'rb') - # contents = bitstream.read() + # contents = open(bitstream, 'rb') # bitstream.close() - #else: - contents = bitstream + # else: + # contents = bitstream + # bitstream = open(filename, 'rb').read() valid_string = '\xff\xff\x00\x00\x00\xdd\x88\x44\x00\x22\xff\xff' # check if the valid header substring exists - if contents.find(valid_string) == 30: - return True + if bitstream.find(valid_string) == 30: + return True, bitstream else: - read_header = contents[30:41] + # Swap header endianness and compare again + swapped_string = '\xff\xff\x00\x00\xdd\x00\x44\x88\x22\x00' + + if bitstream.find(swapped_string) == 30: + # Input bitstream has its endianness swapped + reordered_bitstream = self.reorder_bytes_in_bitstream(bitstream) + return True, reordered_bitstream + + # else: Still problem + read_header = bitstream[30:41] LOGGER.error( 'Incompatible bitstream detected.\nExpected header: {}\nRead ' 'header: {}'.format(repr(valid_string), repr(read_header))) - return False + return False, None @staticmethod def reorder_bytes_in_bin_file(filename, extract_to_disk=False): @@ -2780,6 +2580,22 @@ def reorder_bytes_in_bin_file(filename, extract_to_disk=False): return bitstream + @staticmethod + def reorder_bytes_in_bitstream(bitstream): + """ + Reorders the bytes in a given binary bitstream to make it compatible for + programming the SKARAB. This function only handles the case where + the two bytes making up a word need to be swapped. + :param bitstream: binary bitstream to reorder + :return: reordered_bitstream + """ + num_words = len(bitstream) / 2 + data_format_pack = '<' + str(num_words) + 'H' + data_format_unpack = '>' + str(num_words) + 'H' + unpacked_format = struct.unpack(data_format_unpack, bitstream) + reordered_bitstream = struct.pack(data_format_pack, *unpacked_format) + return reordered_bitstream + def get_system_information(self, filename=None, fpg_info=None): """ Get information about the design running on the FPGA. @@ -2790,40 +2606,15 @@ def get_system_information(self, filename=None, fpg_info=None): dictionaries :return: the information is populated in the class """ - if (filename is None) and (fpg_info is None): - raise RuntimeError( - 'Either filename or parsed fpg data must be given.') - if filename is not None: - device_dict, memorymap_dict = parse_fpg(filename) - else: - device_dict = fpg_info[0] - memorymap_dict = fpg_info[1] - # add system registers - device_dict.update(self._CasperFpga__add_sys_registers()) - # reset current devices and create new ones from the new - # design information - self._CasperFpga__reset_device_info() - self._CasperFpga__create_memory_devices(device_dict, memorymap_dict) - self._CasperFpga__create_other_devices(device_dict) - self.__create_memory_map() - # populate some system information - try: - self.system_info.update(device_dict['77777']) - except KeyError: - LOGGER.warn('%s: no sys info key in design info!' % self.host) - # and RCS information if included - if '77777_git' in device_dict: - self.rcs_info['git'] = device_dict['77777_git'] - if '77777_svn' in device_dict: - self.rcs_info['svn'] = device_dict['77777_svn'] - - def __create_memory_map(self): - """ - Fixes the memory mapping for SKARAB registers by masking the most - significant bit of the register address parsed from the fpg file. - :return: nothing - """ + return filename, fpg_info + def post_get_system_information(self): + """ + Cleanup run after get_system_information + :return: + """ + # Fix the memory mapping for SKARAB registers by masking the most + # significant bit of the register address parsed from the fpg file. for key in self.memory_devices.keys(): self.memory_devices[key].address &= 0x7fffffff @@ -3043,49 +2834,72 @@ def get_spartan_firmware_version(self): version_number = str(major) + '.' + str(minor) return version_number + @staticmethod + def extract_md5_from_fpg(filename): + """ + Given an FPG, extract the md5sum, if it exists + :param filename: + :return: + """ + if filename[-3:] != 'fpg': + errstr = '%s does not seem to be an .fpg file.' % filename + LOGGER.error(errstr) + raise InvalidSkarabBitstream(errstr) + fptr = None + md5_header = None + md5_bitstream = None + try: + fptr = open(filename, 'rb') + fline = fptr.readline() + if not fline.startswith('#!/bin/kcpfpg'): + errstr = '%s does not seem to be a valid .fpg file.' % filename + LOGGER.error(errstr) + raise InvalidSkarabBitstream(errstr) + while not fline.startswith('?quit'): + fline = fptr.readline().strip('\n') + sep = '\t' if fline.startswith('?meta\t') else ' ' + if 'md5_header' in fline: + md5_header = fline.split(sep)[-1] + elif 'md5_bitstream' in fline: + md5_bitstream = fline.split(sep)[-1] + if md5_bitstream is not None and md5_bitstream is not None: + break + except IOError: + errstr = 'Could not open %s.' % filename + LOGGER.error(errstr) + raise IOError(errstr) + finally: + if fptr: + fptr.close() + return md5_header, md5_bitstream + def compare_md5_checksums(self, filename): - ''' - Easier way to do comparisons against the MD5 Checksums in the .fpg file header. Two MD5 Checksums: + """ + Easier way to do comparisons against the MD5 Checksums in the .fpg + file header. Two MD5 Checksums: - md5_header: MD5 Checksum calculated on the .fpg-header - - md5_bitstream: MD5 Checksum calculated on the actual bitstream, starting after '?quit' + - md5_bitstream: MD5 Checksum calculated on the actual bitstream, + starting after '?quit' :param filename: Of the input .fpg file to be analysed :return: Boolean - True/False - 1/0 - Success/Fail - ''' - - # Before we kick off, make sure the input file is indeed an .fpg file - file_extension = os.path.splitext(filename)[1] - - if file_extension != '.fpg': - # Input file was not an fpg file - errmsg = "Input file was not an fpg file" - LOGGER.error(errmsg) - raise InvalidSkarabBitstream(errmsg) - - # Extract bitstream from the .fpg file - bitstream = self.extract_bitstream(filename) - - # First, tokenize the meta-data in the .fpg-header - self.get_system_information(filename) - meta_data_dict = self.system_info - - if 'md5_bitstream' in meta_data_dict.keys(): + """ + (md5_header, md5_bitstream) = self.extract_md5_from_fpg(filename) + if md5_header is not None and md5_bitstream is not None: # Calculate and compare MD5 sums here, before carrying on - fpgfile_md5sum = meta_data_dict['md5_bitstream'] # system_info is a dictionary + # Extract bitstream from the .fpg file + bitstream = self.extract_bitstream(filename) bitstream_md5sum = hashlib.md5(bitstream).hexdigest() - - if bitstream_md5sum != fpgfile_md5sum: - # Problem + if bitstream_md5sum != md5_bitstream: errmsg = "bitstream_md5sum != fpgfile_md5sum" LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) else: # .fpg file was created using an older version of mlib_devel - errmsg = "An older version of mlib_devel generated " + filename + "." \ - " Please update to include the md5sum on the bitstream in the .fpg header." + errmsg = 'An older version of mlib_devel generated ' + \ + filename + '. Please update to include the md5sum ' \ + 'on the bitstream in the .fpg header.' LOGGER.error(errmsg) raise InvalidSkarabBitstream(errmsg) - # If it got here, checksums matched return True - # end