def __init__(self, label, log): self.log = log.getChild("RfdcRegs") self.regs = UIO(label=label, read_only=False) self.poke32 = self.regs.poke32 self.peek32 = self.regs.peek32 # Index corresponds to dboard number. self._converter_chains_in_reset = True
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 __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 dump_jesd_core(self): " Debug method to dump all JESD core regs " dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx), read_only=False) for i in range(0x2000, 0x2110, 0x10): print(("0x%04X " % i), end=' ') for j in range(0, 0x10, 0x4): print(("%08X" % dboard_ctrl_regs.peek32(i + j)), end=' ') print("") dboard_ctrl_regs = None
def dump_jesd_core(self): """ Debug for reading out all JESD core registers via RPC shell """ radio_regs = UIO(label="dboard-regs-{}".format(self.slot_idx)) for i in range(0x2000, 0x2110, 0x10): print(("0x%04X " % i), end=' ') for j in range(0, 0x10, 0x4): print(("%08X" % radio_regs.peek32(i + j)), end=' ') print("")
def dbcore_peek(self, addr): """ Debug for accessing the DB Core registers via the RPC shell. """ dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx), read_only=False) rd_data = dboard_ctrl_regs.peek32(addr) self.log.trace("DB Core Register 0x{:04X} response: 0x{:08X}".format( addr, rd_data)) dboard_ctrl_regs = None return rd_data
def dbcore_poke(self, addr, data): """ Debug for accessing the DB Core registers via the RPC shell. """ dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx), read_only=False) self.log.trace( "Writing DB Core Register 0x{:04X} with 0x{:08X}...".format( addr, data)) dboard_ctrl_regs.poke32(addr, data) dboard_ctrl_regs = None
def _init_dboard_regs(): " Create a UIO object to talk to dboard regs " self.log.trace("Getting uio...") return UIO( label="dboard-regs-{}".format(self.slot_idx), read_only=False )
def __init__(self, label, log): self.log = log.getChild("CtrlportRegs") self._regs_uio_opened = False try: self.regs = UIO(label=label, read_only=False) except RuntimeError: self.log.warning('Ctrlport regs could not be found. ' \ 'MPM Endpoint to the FPGA is not part of this image.') self.regs = None # Initialize SPI interface to MB PL CPLD and DB CPLDs self.set_mb_pl_cpld_divider(self.min_mb_cpld_spi_divider) self.set_db_divider_value(self.min_db_cpld_spi_divider) self.mb_pl_cpld_regs = self.MbPlCpldIface(self, self.MB_PL_CPLD, self.log) self.mb_pl_cpld_regs.check_signature() self.mb_pl_cpld_regs.check_revision() self.db_0_regs = self.DbCpldIface(self, self.DB_0_CPLD) self.db_1_regs = self.DbCpldIface(self, self.DB_1_CPLD)
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 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 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: 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 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 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 ""
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
class RfdcRegsControl: """ Control the FPGA RFDC registers external to the XRFdc API """ # pylint: disable=bad-whitespace IQ_SWAP_OFFSET = 0x10000 MMCM_RESET_BASE_OFFSET = 0x11000 RF_RESET_CONTROL_OFFSET = 0x12000 RF_RESET_STATUS_OFFSET = 0x12008 RF_STATUS_OFFSET = 0x13000 FABRIC_DSP_INFO_OFFSET = 0x13008 CAL_DATA_OFFSET = 0x14000 CAL_ENABLE_OFFSET = 0x14008 THRESHOLD_STATUS_OFFSET = 0x15000 RF_PLL_CONTROL_OFFSET = 0x16000 RF_PLL_STATUS_OFFSET = 0x16008 # pylint: enable=bad-whitespace def __init__(self, label, log): self.log = log.getChild("RfdcRegs") self.regs = UIO(label=label, read_only=False) self.poke32 = self.regs.poke32 self.peek32 = self.regs.peek32 # Index corresponds to dboard number. self._converter_chains_in_reset = True def get_threshold_status(self, slot_id, channel, threshold_idx): """ Retrieves the status bit for the given threshold block """ BITMASKS = { (0, 0, 0): 0x04, (0, 0, 1): 0x08, (0, 1, 0): 0x01, (0, 1, 1): 0x02, (1, 0, 0): 0x400, (1, 0, 1): 0x800, (1, 1, 0): 0x100, (1, 1, 1): 0x200, } assert (slot_id, channel, threshold_idx) in BITMASKS status = self.peek(self.THRESHOLD_STATUS_OFFSET) status_bool = (status & BITMASKS[(slot_id, channel, threshold_idx)]) != 0 return 1 if status_bool else 0 def set_cal_data(self, i, q): assert 0 <= i < 2**16 assert 0 <= q < 2**16 self.poke(self.CAL_DATA_OFFSET, (q << 16) | i) def set_cal_enable(self, channel, enable): assert 0 <= channel <= 3 assert enable in [False, True] en = self.peek(self.CAL_ENABLE_OFFSET) bit_offsets = { 0: 0, 1: 1, 2: 4, 3: 5, } en_mask = 1 << bit_offsets[channel] en = en & ~en_mask self.poke(self.CAL_ENABLE_OFFSET, en | (en_mask if enable else 0)) def enable_iq_swap(self, enable, db_id, block_id, is_dac): iq_swap_bit = (int(is_dac) * 8) + (db_id * 4) + block_id # Write IQ swap bit with a mask reg_val = self.peek(self.IQ_SWAP_OFFSET) reg_val = (reg_val & ~(1 << iq_swap_bit)) \ | (enable << iq_swap_bit) self.poke(self.IQ_SWAP_OFFSET, reg_val) def set_reset_mmcm(self, reset=True): if reset: # Put the MMCM in reset (active low) self.poke(self.MMCM_RESET_BASE_OFFSET, 0) else: # Take the MMCM out of reset self.poke(self.MMCM_RESET_BASE_OFFSET, 1) def wait_for_mmcm_locked(self, timeout=0.001): """ Wait for MMCM to come to a stable locked state. The datasheet specifies a 100us max lock time """ DATA_CLK_PLL_LOCKED = 1 << 20 POLL_SLEEP = 0.0002 for _ in range(int(timeout / POLL_SLEEP)): time.sleep(POLL_SLEEP) status = self.peek(self.RF_PLL_STATUS_OFFSET) if status & DATA_CLK_PLL_LOCKED: self.log.trace("RF MMCM lock detected.") return self.log.error("MMCM failed to lock in the expected time.") raise RuntimeError("MMCM failed to lock within the expected time.") def set_gated_clock_enables(self, value=True): """ Controls the clock enable for data_clk and data_clk_2x """ ENABLE_DATA_CLK = 1 ENABLE_DATA_CLK_2X = 1 << 4 ENABLE_RF_CLK = 1 << 8 ENABLE_RF_CLK_2X = 1 << 12 if value: # Enable buffers gating the clocks self.poke( self.RF_PLL_CONTROL_OFFSET, ENABLE_DATA_CLK | ENABLE_DATA_CLK_2X | ENABLE_RF_CLK | ENABLE_RF_CLK_2X) else: # Disable clock buffers to have clocks gated. self.poke(self.RF_PLL_CONTROL_OFFSET, 0) def get_fabric_dsp_info(self, dboard): """ Read the DSP information register and returns the DSP bandwidth, rx channel count and tx channel count """ # Offsets DSP_BW = 0 + 16 * dboard DSP_RX_CNT = 12 + 16 * dboard DSP_TX_CNT = 14 + 16 * dboard # Masks DSP_BW_MSK = 0xFFF DSP_RX_CNT_MSK = 0x3 DSP_TX_CNT_MSK = 0x3 dsp_info = self.peek(self.FABRIC_DSP_INFO_OFFSET) self.log.trace("Fabric DSP for dboard %d...", dboard) dsp_bw = (dsp_info >> DSP_BW) & DSP_BW_MSK self.log.trace(" Bandwidth (MHz): %d", dsp_bw) dsp_rx_cnt = (dsp_info >> DSP_RX_CNT) & DSP_RX_CNT_MSK self.log.trace(" Rx channel count: %d", dsp_rx_cnt) dsp_tx_cnt = (dsp_info >> DSP_TX_CNT) & DSP_TX_CNT_MSK self.log.trace(" Tx channel count: %d", dsp_tx_cnt) return [dsp_bw, dsp_rx_cnt, dsp_tx_cnt] def get_rfdc_resampling_factor(self, dboard): """ Returns the appropriate decimation/interpolation factor to set in the RFDC. """ # DSP vs. RFDC decimation/interpolation dictionary # Key: bandwidth in MHz # Value: (RFDC resampling factor, is Half-band resampling used?) RFDC_RESAMPLING_FACTOR = { 100: (8, False), # 100 MHz BW requires 8x RFDC resampling 200: (2, True), # 200 MHz BW requires 2x RFDC resampling # (400 MHz RFDC DSP used w/ half-band resampling) 400: (2, False) # 400 MHz BW requires 2x RFDC resampling } dsp_bw, _, _ = self.get_fabric_dsp_info(dboard) # When no RF fabric DSP is present (dsp_bw = 0), MPM should # simply use the default RFDC resampling factor (400 MHz). if dsp_bw in RFDC_RESAMPLING_FACTOR: rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[dsp_bw] else: rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[400] self.log.trace(" Using default resampling!") self.log.trace(" RFDC resampling: %d", rfdc_resampling_factor) return (rfdc_resampling_factor, halfband) def set_reset_adc_dac_chains(self, reset=True): """ Resets or enables the ADC and DAC chain for the given dboard """ def _wait_for_done(done_bit, timeout=5): """ Wait for the specified sequence done bit when resetting or enabling an ADC or DAC chain. Throws an error on timeout. """ status = self.peek(self.RF_RESET_STATUS_OFFSET) if (status & done_bit): return for _ in range(0, timeout): time.sleep(0.001) # 1 ms status = self.peek(self.RF_RESET_STATUS_OFFSET) if (status & done_bit): return self.log.error( "Timeout while resetting or enabling ADC/DAC chains.") raise RuntimeError( "Timeout while resetting or enabling ADC/DAC chains.") # CONTROL OFFSET ADC_RESET = 1 << 4 DAC_RESET = 1 << 8 # STATUS OFFSET ADC_SEQ_DONE = 1 << 7 DAC_SEQ_DONE = 1 << 11 if reset: if self._converter_chains_in_reset: self.log.debug('Converters are already in reset. ' 'The reset bit will NOT be toggled.') return # Reset the ADC and DAC chains self.log.trace('Resetting ADC chain') self.poke(self.RF_RESET_CONTROL_OFFSET, ADC_RESET) _wait_for_done(ADC_SEQ_DONE) self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) self.log.trace('Resetting DAC chain') self.poke(self.RF_RESET_CONTROL_OFFSET, DAC_RESET) _wait_for_done(DAC_SEQ_DONE) self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) self._converter_chains_in_reset = True else: # enable self._converter_chains_in_reset = False def log_status(self): status = self.peek(self.RF_STATUS_OFFSET) self.log.debug("Daughterboard 0") self.log.debug(" @RFDC") self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 0) & 0x3)) self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 2) & 0x3)) self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 6) & 0x3)) self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 10) & 0x3)) self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 4) & 0x3)) self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 8) & 0x3)) self.log.debug(" @USER") self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 12) & 0x3)) self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 14) & 0x3)) self.log.debug("Daughterboard 1") self.log.debug(" @RFDC") self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 16) & 0x3)) self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 18) & 0x3)) self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 22) & 0x3)) self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 26) & 0x3)) self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 20) & 0x3)) self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 24) & 0x3)) self.log.debug(" @USER") self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 28) & 0x3)) self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 30) & 0x3)) def poke(self, addr, val): with self.regs: self.regs.poke32(addr, val) def peek(self, addr): with self.regs: result = self.regs.peek32(addr) return result
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
class CtrlportRegs: """ Control the FPGA Ctrlport registers """ # pylint: disable=bad-whitespace IPASS_OFFSET = 0x000010 MB_PL_SPI_CONFIG = 0x000020 DB_SPI_CONFIG = 0x000024 MB_PL_CPLD = 0x008000 DB_0_CPLD = 0x010000 DB_1_CPLD = 0x018000 # pylint: enable=bad-whitespace min_mb_cpld_spi_divider = 2 min_db_cpld_spi_divider = 5 class MbPlCpldIface: """ Exposes access to register mapped MB PL CPLD register space """ SIGNATURE_OFFSET = 0x0000 REVISION_OFFSET = 0x0004 SIGNATURE = 0x3FDC5C47 MIN_REQ_REVISION = 0x20082009 def __init__(self, regs_iface, offset, log): self.log = log self.offset = offset self.regs = regs_iface def peek32(self, addr): return self.regs.peek32(addr + self.offset) def poke32(self, addr, val): self.regs.poke32(addr + self.offset, val) def check_signature(self): read_signature = self.peek32(self.SIGNATURE_OFFSET) if self.SIGNATURE != read_signature: self.log.error('MB PL CPLD signature {:X} does not match ' 'expected value {:X}'.format( read_signature, self.SIGNATURE)) raise RuntimeError('MB PL CPLD signature {:X} does not match ' 'expected value {:X}'.format( read_signature, self.SIGNATURE)) def check_revision(self): read_revision = self.peek32(self.REVISION_OFFSET) if read_revision < self.MIN_REQ_REVISION: error_message = ( 'MB PL CPLD revision {:X} is out of date. ' 'Expected value {:X}. Update your CPLD image.'.format( read_revision, self.MIN_REQ_REVISION)) self.log.error(error_message) raise RuntimeError(error_message) class DbCpldIface: """ Exposes access to register mapped DB CPLD register spaces """ def __init__(self, regs_iface, offset): self.offset = offset self.regs = regs_iface def peek32(self, addr): return self.regs.peek32(addr + self.offset) def poke32(self, addr, val): self.regs.poke32(addr + self.offset, val) def __init__(self, label, log): self.log = log.getChild("CtrlportRegs") self._regs_uio_opened = False try: self.regs = UIO(label=label, read_only=False) except RuntimeError: self.log.warning('Ctrlport regs could not be found. ' \ 'MPM Endpoint to the FPGA is not part of this image.') self.regs = None # Initialize SPI interface to MB PL CPLD and DB CPLDs self.set_mb_pl_cpld_divider(self.min_mb_cpld_spi_divider) self.set_db_divider_value(self.min_db_cpld_spi_divider) self.mb_pl_cpld_regs = self.MbPlCpldIface(self, self.MB_PL_CPLD, self.log) self.mb_pl_cpld_regs.check_signature() self.mb_pl_cpld_regs.check_revision() self.db_0_regs = self.DbCpldIface(self, self.DB_0_CPLD) self.db_1_regs = self.DbCpldIface(self, self.DB_1_CPLD) def init(self): if not self._regs_uio_opened: self.regs._open() self._regs_uio_opened = True def deinit(self): if self._regs_uio_opened: self.regs._close() self._regs_uio_opened = False def peek32(self, addr): if self.regs is None: raise RuntimeError('The ctrlport registers were never configured!') if self._regs_uio_opened: return self.regs.peek32(addr) else: with self.regs: return self.regs.peek32(addr) def poke32(self, addr, val): if self.regs is None: raise RuntimeError('The ctrlport registers were never configured!') if self._regs_uio_opened: return self.regs.poke32(addr, val) else: with self.regs: return self.regs.poke32(addr, val) def set_mb_pl_cpld_divider(self, divider_value): if not self.min_mb_cpld_spi_divider <= divider_value <= 0xFFFF: self.log.error( 'Cannot set MB CPLD SPI divider to invalid value {}'.format( divider_value)) raise RuntimeError( 'Cannot set MB CPLD SPI divider to invalid value {}'.format( divider_value)) self.poke32(self.MB_PL_SPI_CONFIG, divider_value) def set_db_divider_value(self, divider_value): if not self.min_db_cpld_spi_divider <= divider_value <= 0xFFFF: self.log.error( 'Cannot set DB SPI divider to invalid value {}'.format( divider_value)) raise RuntimeError( 'Cannot set DB SPI divider to invalid value {}'.format( divider_value)) self.poke32(self.DB_SPI_CONFIG, divider_value) def get_db_cpld_iface(self, db_id): return self.db_0_regs if db_id == 0 else self.db_1_regs def get_mb_pl_cpld_iface(self): return self.mb_pl_cpld_regs def enable_cable_present_forwarding(self, enable=True): value = 1 if enable else 0 self.poke32(self.IPASS_OFFSET, value)