def main(): " Dawaj, dawaj! " args = parse_args() # Initialize logger for downstream components mpm.get_main_logger().getChild('main') master_core_uio = UIO(label=args.uio_dev, read_only=False) master_core_uio.open() if args.slave_uio_dev: slave_core_uio = UIO(label=args.uio_dev, read_only=False) slave_core_uio.open() try: master_core = AuroraControl(master_core_uio, args.base_addr) slave_core = None if args.slave_uio_dev is None else AuroraControl( slave_core_uio, args.slave_base_addr, ) if args.loopback: master_core.reset_core() master_core.set_loopback(enable=True) return True # Run BIST if args.test == 'ber': print("Performing BER BIST test.") master_core.run_ber_loopback_bist( args.duration, args.rate * 8e6, slave_core, ) else: print("Performing Latency BIST test.") master_core.run_latency_loopback_bist( args.duration, args.rate * 8e6, slave_core, ) except Exception as ex: print("Unexpected exception: {}".format(str(ex))) return False finally: master_core_uio.close() if args.slave_uio_dev: slave_core_uio.close()
class LiberioDispatcherTable(object): """ Controls a Liberio DMA dispatcher table. label -- A label that can be used by udev to find a UIO device """ def __init__(self, label): self.log = get_logger(label) self._regs = UIO(label=label, read_only=False) self.poke32 = self._regs.poke32 self.peek32 = self._regs.peek32 def set_route(self, sid, dma_channel): """ Sets up routing in the Liberio dispatcher. From sid, only the destination part is important. After this call, any CHDR packet with the appropriate destination address will get routed to `dma_channel`. sid -- Full SID, but only destination part matters. dma_channel -- The DMA channel to which these packets should get routed. """ self.log.debug( "Routing SID `{sid}' to DMA channel `{chan}'.".format( sid=str(sid), chan=dma_channel ) ) def poke_and_trace(addr, data): " Do a poke32() and log.trace() " self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( addr, data )) self.poke32(addr, data) # Poke reg for destination channel try: with self._regs.open(): poke_and_trace( 0 + 4 * sid.dst_ep, dma_channel, ) except Exception as ex: self.log.error( "Unexpected exception while setting route: %s", str(ex), ) raise
class WhiteRabbitRegsControl(object): """ Control and read the FPGA White Rabbit core registers """ # Memory Map # 0x00000000: I/D Memory # 0x00020000: Peripheral interconnect # +0x000: Minic # +0x100: Endpoint # +0x200: Softpll # +0x300: PPS gen # +0x400: Syscon # +0x500: UART # +0x600: OneWire # +0x700: Auxillary space (Etherbone config, etc) # +0x800: WRPC diagnostics registers PERIPH_INTERCON_BASE = 0x20000 # PPS_GEN Map PPSG_ESCR = 0x31C def __init__(self, label, log): self.log = log self.regs = UIO( label=label, read_only=False ) self.periph_peek32 = lambda addr: self.regs.peek32(addr + self.PERIPH_INTERCON_BASE) self.periph_poke32 = lambda addr, data: self.regs.poke32(addr + self.PERIPH_INTERCON_BASE, data) def get_time_lock_status(self): """ Retrieves and decodes the lock status for the PPS out of the WR core. """ with self.regs.open(): ext_sync_status = self.periph_peek32(self.PPSG_ESCR) # bit 2: PPS_VALID # bit 3: TM_VALID (timecode) # All other bits MUST be ignored since they are not guaranteed to be zero or # stable! return (ext_sync_status & 0b1100) == 0b1100
class MboardRegsControl(object): """ Control the FPGA Motherboard registers """ # Motherboard registers M_COMPAT_NUM = 0x0000 MB_DATESTAMP = 0x0004 MB_GIT_HASH = 0x0008 MB_SCRATCH = 0x000C MB_NUM_CE = 0x0010 MB_NUM_IO_CE = 0x0014 MB_CLOCK_CTRL = 0x0018 MB_XADC_RB = 0x001C MB_BUS_CLK_RATE = 0x0020 MB_BUS_COUNTER = 0x0024 MB_SFP0_INFO = 0x0028 MB_SFP1_INFO = 0x002C MB_GPIO_MASTER = 0x0030 MB_GPIO_RADIO_SRC = 0x0034 # Bitfield locations for the MB_CLOCK_CTRL register. MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded! MB_CLOCK_CTRL_PPS_SEL_INT_25 = 1 MB_CLOCK_CTRL_PPS_SEL_EXT = 2 MB_CLOCK_CTRL_PPS_SEL_GPSDO = 3 MB_CLOCK_CTRL_PPS_OUT_EN = 4 # output enabled = 1 MB_CLOCK_CTRL_MEAS_CLK_RESET = 12 # set to 1 to reset mmcm, default is 0 MB_CLOCK_CTRL_MEAS_CLK_LOCKED = 13 # locked indication for meas_clk mmcm def __init__(self, label, log): self.log = log self.regs = UIO(label=label, read_only=False) self.poke32 = self.regs.poke32 self.peek32 = self.regs.peek32 def get_compat_number(self): """get FPGA compat number This function reads back FPGA compat number. The return is a tuple of 2 numbers: (major compat number, minor compat number ) """ with self.regs.open(): compat_number = self.peek32(self.M_COMPAT_NUM) minor = compat_number & 0xff major = (compat_number >> 16) & 0xff return (major, minor) def set_fp_gpio_master(self, value): """set driver for front panel GPIO Arguments: value {unsigned} -- value is a single bit bit mask of 12 pins GPIO """ with self.regs.open(): return self.poke32(self.MB_GPIO_MASTER, value) def get_fp_gpio_master(self): """get "who" is driving front panel gpio The return value is a bit mask of 12 pins GPIO. 0: means the pin is driven by PL 1: means the pin is driven by PS """ with self.regs.open(): return self.peek32(self.MB_GPIO_MASTER) & 0xfff def set_fp_gpio_radio_src(self, value): """set driver for front panel GPIO Arguments: value {unsigned} -- value is 2-bit bit mask of 12 pins GPIO 00: means the pin is driven by radio 0 01: means the pin is driven by radio 1 10: means the pin is driven by radio 2 11: means the pin is driven by radio 3 """ with self.regs.open(): return self.poke32(self.MB_GPIO_RADIO_SRC, value) def get_fp_gpio_radio_src(self): """get which radio is driving front panel gpio The return value is 2-bit bit mask of 12 pins GPIO. 00: means the pin is driven by radio 0 01: means the pin is driven by radio 1 10: means the pin is driven by radio 2 11: means the pin is driven by radio 3 """ with self.regs.open(): return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff def get_build_timestamp(self): """ Returns the build date/time for the FPGA image. The return is datetime string with the ISO 8601 format (YYYY-MM-DD HH:MM:SS.mmmmmm) """ with self.regs.open(): datestamp_rb = self.peek32(self.MB_DATESTAMP) if datestamp_rb > 0: dt_str = datetime.datetime(year=((datestamp_rb >> 17) & 0x3F) + 2000, month=(datestamp_rb >> 23) & 0x0F, day=(datestamp_rb >> 27) & 0x1F, hour=(datestamp_rb >> 12) & 0x1F, minute=(datestamp_rb >> 6) & 0x3F, second=((datestamp_rb >> 0) & 0x3F)) self.log.trace("FPGA build timestamp: {}".format(str(dt_str))) return str(dt_str) else: # Compatibility with FPGAs without datestamp capability return '' def get_git_hash(self): """ Returns the GIT hash for the FPGA build. The return is a tuple of 2 numbers: (short git hash, bool: is the tree dirty?) """ with self.regs.open(): git_hash_rb = self.peek32(self.MB_GIT_HASH) git_hash = git_hash_rb & 0x0FFFFFFF tree_dirty = ((git_hash_rb & 0xF0000000) > 0) dirtiness_qualifier = 'dirty' if tree_dirty else 'clean' self.log.trace("FPGA build GIT Hash: {:07x} ({})".format( git_hash, dirtiness_qualifier)) return (git_hash, dirtiness_qualifier) def set_time_source(self, time_source, ref_clk_freq): """ Set time source """ pps_sel_val = 0x0 if time_source == 'internal': assert ref_clk_freq in (10e6, 25e6) if ref_clk_freq == 10e6: self.log.trace("Setting time source to internal " "(10 MHz reference)...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_10 elif ref_clk_freq == 25e6: self.log.trace("Setting time source to internal " "(25 MHz reference)...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_25 elif time_source == 'external': self.log.trace("Setting time source to external...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT elif time_source == 'gpsdo': self.log.trace("Setting time source to gpsdo...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_GPSDO else: assert False with self.regs.open(): reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFFF0 reg_val = reg_val | (pps_sel_val & 0xF) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def enable_pps_out(self, enable): """ Enables the PPS/Trig output on the back panel """ self.log.trace("%s PPS/Trig output!", "Enabling" if enable else "Disabling") mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN) with self.regs.open(): # mask the bit to clear it: reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask if enable: # set the bit if desired: reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def reset_meas_clk_mmcm(self, reset=True): """ Reset or unreset the MMCM for the measurement clock in the FPGA TDC. """ self.log.trace("%s measurement clock MMCM reset...", "Asserting" if reset else "Clearing") mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET) with self.regs.open(): # mask the bit to clear it reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask if reset: # set the bit if desired reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def get_meas_clock_mmcm_lock(self): """ Check the status of the MMCM for the measurement clock in the FPGA TDC. """ mask = 0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_LOCKED with self.regs.open(): reg_val = self.peek32(self.MB_CLOCK_CTRL) locked = (reg_val & mask) > 0 if not locked: self.log.warning("Measurement clock MMCM reporting unlocked. " "MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val)) else: self.log.trace("Measurement clock MMCM locked!") return locked def get_fpga_type(self): """ Reads the type of the FPGA image currently loaded Returns a string with the type (ie HG, XG, AA, etc.) """ with self.regs.open(): sfp0_info_rb = self.peek32(self.MB_SFP0_INFO) sfp1_info_rb = self.peek32(self.MB_SFP1_INFO) # Print the registers values as 32-bit hex values self.log.trace("SFP0 Info: 0x{0:0{1}X}".format(sfp0_info_rb, 8)) self.log.trace("SFP1 Info: 0x{0:0{1}X}".format(sfp1_info_rb, 8)) sfp0_type = N3XX_SFP_TYPES.get((sfp0_info_rb & 0x0000FF00) >> 8, "") sfp1_type = N3XX_SFP_TYPES.get((sfp1_info_rb & 0x0000FF00) >> 8, "") self.log.trace("SFP types: ({}, {})".format(sfp0_type, sfp1_type)) if (sfp0_type == "") or (sfp1_type == ""): return "" elif (sfp0_type == "1G") and (sfp1_type == "10G"): return "HG" elif (sfp0_type == "10G") and (sfp1_type == "10G"): return "XG" elif (sfp0_type == "10G") and (sfp1_type == "A"): return "XA" elif (sfp0_type == "A") and (sfp1_type == "A"): return "AA" else: self.log.warning( "Unrecognized SFP type combination: ({}, {})".format( sfp0_type, sfp1_type)) return ""
class EthDispatcherTable(object): """ Controls an Ethernet dispatcher table. """ DEFAULT_VITA_PORT = (49153, 49154) # Address offsets: OWN_IP_OFFSET = 0x0000 OWN_PORT_OFFSET = 0x0004 FORWARD_ETH_BCAST_OFFSET = 0x0008 SID_IP_OFFSET = 0x1000 SID_PORT_MAC_HI_OFFSET = 0x1400 SID_MAC_LO_OFFSET = 0x1800 def __init__(self, label): self.log = get_logger(label) self._regs = UIO(label=label, read_only=False) self.poke32 = self._regs.poke32 self.peek32 = self._regs.peek32 def set_ipv4_addr(self, ip_addr): """ Set the own IPv4 address for this Ethernet dispatcher. Outgoing packets will have this IP address. """ self.log.debug("Setting my own IP address to `{}'".format(ip_addr)) ip_addr_int = int(netaddr.IPAddress(ip_addr)) with self._regs.open(): self.poke32(self.OWN_IP_OFFSET, ip_addr_int) def set_vita_port(self, port_value=None, port_idx=None): """ Set the port that is used for incoming VITA traffic. This is used to distinguish traffic that goes to the FPGA from that going to the ARM. """ port_idx = port_idx or 0 port_value = port_value or self.DEFAULT_VITA_PORT[port_idx] assert port_idx in (0) #FIXME: Fix port_idx = 1 port_reg_addr = self.OWN_PORT_OFFSET with self._regs.open(): self.poke32(port_reg_addr, port_value) def set_route(self, sid, ip_addr, udp_port, mac_addr=None): """ Sets up routing in the Ethernet dispatcher. From sid, only the destination part is important. After this call, any CHDR packet reaching this Ethernet dispatcher will get routed to `ip_addr' and `udp_port'. It automatically looks up the MAC address of the destination unless a MAC address is given. sid -- Full SID, but only destination part matters. ip_addr -- IPv4 destination address. String format ("1.2.3.4"). udp_addr -- Destination UDP port. mac_addr -- If given, this will replace an ARP lookup for the MAC address. String format, ("aa:bb:cc:dd:ee:ff"), case does not matter. """ udp_port = int(udp_port) if mac_addr is None: mac_addr = get_mac_addr(ip_addr) if mac_addr is None: self.log.error( "Could not resolve a MAC address for IP address `{}'".format( ip_addr)) dst_ep = sid.dst_ep self.log.debug( "Routing SID `{sid}' (endpoint `{ep}') to IP address `{ip}', " \ "MAC address `{mac}', port `{port}'".format( sid=str(sid), ep=dst_ep, ip=ip_addr, mac=mac_addr, port=udp_port ) ) ip_addr_int = int(netaddr.IPAddress(ip_addr)) mac_addr_int = int(netaddr.EUI(mac_addr)) sid_offset = 4 * dst_ep def poke_and_trace(addr, data): " Do a poke32() and log.trace() " self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( addr, data)) self.poke32(addr, data) with self._regs.open(): poke_and_trace(self.SID_IP_OFFSET + sid_offset, ip_addr_int) poke_and_trace( self.SID_MAC_LO_OFFSET + sid_offset, mac_addr_int & 0xFFFFFFFF, ) poke_and_trace(self.SID_PORT_MAC_HI_OFFSET + sid_offset, (udp_port << 16) | (mac_addr_int >> 32)) def set_forward_policy(self, forward_eth, forward_bcast): """ Forward Ethernet packet not matching OWN_IP to CROSSOVER Forward broadcast packet to CPU and CROSSOVER """ reg_value = int(bool(forward_eth) << 1) | int(bool(forward_bcast)) self.log.trace("Writing to address 0x{:04X}: 0x{:04X}".format( self.FORWARD_ETH_BCAST_OFFSET, reg_value)) with self._regs.open(): self.poke32(self.FORWARD_ETH_BCAST_OFFSET, reg_value)
class MboardRegsControl(object): """ Control the FPGA Motherboard registers """ # Motherboard registers M_COMPAT_NUM = 0x0000 MB_DATESTAMP = 0x0004 MB_GIT_HASH = 0x0008 MB_SCRATCH = 0x000C MB_NUM_CE = 0x0010 MB_NUM_IO_CE = 0x0014 MB_CLOCK_CTRL = 0x0018 MB_XADC_RB = 0x001C MB_BUS_CLK_RATE = 0x0020 MB_BUS_COUNTER = 0x0024 # Bitfield locations for the MB_CLOCK_CTRL register. MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded! MB_CLOCK_CTRL_PPS_SEL_INT_25 = 1 MB_CLOCK_CTRL_PPS_SEL_EXT = 2 MB_CLOCK_CTRL_PPS_SEL_GPSDO = 3 MB_CLOCK_CTRL_PPS_OUT_EN = 4 # output enabled = 1 MB_CLOCK_CTRL_MEAS_CLK_RESET = 12 # set to 1 to reset mmcm, default is 0 MB_CLOCK_CTRL_MEAS_CLK_LOCKED = 13 # locked indication for meas_clk mmcm def __init__(self, label, log): self.log = log self.regs = UIO(label=label, read_only=False) self.poke32 = self.regs.poke32 self.peek32 = self.regs.peek32 def get_compat_number(self): """get FPGA compat number This function reads back FPGA compat number. The return is a tuple of 2 numbers: (major compat number, minor compat number ) """ with self.regs.open(): compat_number = self.peek32(self.M_COMPAT_NUM) minor = compat_number & 0xff major = (compat_number >> 16) & 0xff return (major, minor) def get_build_timestamp(self): """ Returns the build date/time for the FPGA image. The return is datetime string with the ISO 8601 format (YYYY-MM-DD HH:MM:SS.mmmmmm) """ with self.regs.open(): datestamp_rb = self.peek32(self.MB_DATESTAMP) if datestamp_rb > 0: dt_str = datetime.datetime(year=((datestamp_rb >> 17) & 0x3F) + 2000, month=(datestamp_rb >> 23) & 0x0F, day=(datestamp_rb >> 27) & 0x1F, hour=(datestamp_rb >> 12) & 0x1F, minute=(datestamp_rb >> 6) & 0x3F, second=((datestamp_rb >> 0) & 0x3F)) self.log.trace("FPGA build timestamp: {}".format(str(dt_str))) return str(dt_str) else: # Compatibility with FPGAs without datestamp capability return '' def get_git_hash(self): """ Returns the GIT hash for the FPGA build. The return is a tuple of 2 numbers: (short git hash, bool: is the tree dirty?) """ with self.regs.open(): git_hash_rb = self.peek32(self.MB_GIT_HASH) git_hash = git_hash_rb & 0x0FFFFFFF tree_dirty = ((git_hash_rb & 0xF0000000) > 0) dirtiness_qualifier = 'dirty' if tree_dirty else 'clean' self.log.trace("FPGA build GIT Hash: {:07x} ({})".format( git_hash, dirtiness_qualifier)) return (git_hash, dirtiness_qualifier) def set_time_source(self, time_source, ref_clk_freq): """ Set time source """ pps_sel_val = 0x0 if time_source == 'internal': assert ref_clk_freq in (10e6, 25e6) if ref_clk_freq == 10e6: self.log.trace("Setting time source to internal " "(10 MHz reference)...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_10 elif ref_clk_freq == 25e6: self.log.trace("Setting time source to internal " "(25 MHz reference)...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_25 elif time_source == 'external': self.log.trace("Setting time source to external...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT elif time_source == 'gpsdo': self.log.trace("Setting time source to gpsdo...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_GPSDO else: assert False with self.regs.open(): reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFFF0 reg_val = reg_val | (pps_sel_val & 0xF) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def enable_pps_out(self, enable): """ Enables the PPS/Trig output on the back panel """ self.log.trace("%s PPS/Trig output!", "Enabling" if enable else "Disabling") mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN) with self.regs.open(): # mask the bit to clear it: reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask if enable: # set the bit if desired: reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def reset_meas_clk_mmcm(self, reset=True): """ Reset or unreset the MMCM for the measurement clock in the FPGA TDC. """ self.log.trace("%s measurement clock MMCM reset...", "Asserting" if reset else "Clearing") mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET) with self.regs.open(): # mask the bit to clear it reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask if reset: # set the bit if desired reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) def get_meas_clock_mmcm_lock(self): """ Check the status of the MMCM for the measurement clock in the FPGA TDC. """ mask = 0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_LOCKED with self.regs.open(): reg_val = self.peek32(self.MB_CLOCK_CTRL) locked = (reg_val & mask) > 0 if not locked: self.log.warning("Measurement clock MMCM reporting unlocked. " "MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val)) else: self.log.trace("Measurement clock MMCM locked!") return locked