diff options
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager')
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/base.py | 264 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e31x.py | 154 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e31x_periphs.py | 166 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e320.py | 188 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e320_periphs.py | 164 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 199 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py | 67 |
7 files changed, 804 insertions, 398 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index be7600333..4ae30778a 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -177,7 +177,7 @@ class PeriphManagerBase(object): ########################################################################### def __init__(self): # Note: args is a dictionary. - assert len(self.pids) > 0 + assert self.pids assert self.mboard_eeprom_magic is not None self.dboards = [] self._default_args = "" @@ -241,14 +241,17 @@ class PeriphManagerBase(object): If no EEPROM is defined, returns empty values. """ - if len(self.mboard_eeprom_addr): + if self.mboard_eeprom_addr: self.log.trace("Reading EEPROM from address `{}'..." .format(self.mboard_eeprom_addr)) - if (not get_eeprom_paths(self.mboard_eeprom_addr)): - raise RuntimeError("No EEPROM found at address `{}'" - .format(self.mboard_eeprom_addr)) + eeprom_paths = get_eeprom_paths(self.mboard_eeprom_addr) + if not eeprom_paths: + self.log.error("Could not identify EEPROM paths for %s!", + self.mboard_eeprom_addr) + return {}, b'' + self.log.trace("Found mboard EEPROM path: %s", eeprom_paths[0]) (eeprom_head, eeprom_rawdata) = eeprom.read_eeprom( - get_eeprom_paths(self.mboard_eeprom_addr)[0], + eeprom_paths[0], self.mboard_eeprom_offset, eeprom.MboardEEPROM.eeprom_header_format, eeprom.MboardEEPROM.eeprom_header_keys, @@ -512,6 +515,22 @@ class PeriphManagerBase(object): ########################################################################### # Misc device status controls and indicators ########################################################################### + def set_device_id(self, device_id): + """ + Sets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + raise NotImplementedError("set_device_id() not implemented.") + + def get_device_id(self): + """ + Gets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + raise NotImplementedError("get_device_id() not implemented.") + def get_init_status(self): """ Returns the status of the device after its initialization (that happens @@ -592,6 +611,20 @@ class PeriphManagerBase(object): """ return [dboard.device_info for dboard in self.dboards] + @no_claim + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + raise NotImplementedError("get_proto_ver() not implemented.") + + @no_claim + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + raise NotImplementedError("get_chdr_width() not implemented.") + ########################################################################### # Component updating ########################################################################### @@ -670,64 +703,10 @@ class PeriphManagerBase(object): self.log.trace("Component info: {}".format(metadata)) # Convert all values to str return dict([a, str(x)] for a, x in metadata.items()) - else: - self.log.trace("Component not found in updateable components: {}" - .format(component_name)) - return {} - - ########################################################################### - # Crossbar control - ########################################################################### - @no_claim - def get_num_xbars(self): - """ - Returns the number of crossbars instantiated in the current design - """ - return 1 # FIXME - - @no_claim - def get_num_blocks(self, xbar_index): - """ - Returns the number of blocks connected to crossbar with index - xbar_index. - - xbar_index -- The index of the crossbar that's being queried. - docstring for get_num_blocks""" - # FIXME udev lookup - xbar_sysfs_path = '/sys/class/rfnoc_crossbar/crossbar{}/nports'.format( - xbar_index - ) - return int(open(xbar_sysfs_path).read().strip()) - \ - self.get_base_port(xbar_index) - - @no_claim - def get_base_port(self, xbar_index): - """ - Returns the index of the first port which is connected to an RFNoC - block. Example: Assume there are two SFPs connected to the crossbar, and - one DMA engine for CHDR traffic. The convention would be to connect - those to ports 0, 1, and 2, respectively. This makes port 3 the first - block to be connected to an RFNoC block. - - xbar_index -- The index of the crossbar that's being queried - """ - return self.crossbar_base_port - - def set_xbar_local_addr(self, xbar_index, local_addr): - """ - Program crossbar xbar_index to have the local address local_addr. - """ - # FIXME udev lookup - xbar_sysfs_path = '/sys/class/rfnoc_crossbar/crossbar{}/local_addr'.format( - xbar_index - ) - laddr_value = "0x{:X}".format(local_addr) - self.log.trace("Setting local address for xbar {} to {}.".format( - xbar_sysfs_path, laddr_value - )) - with open(xbar_sysfs_path, "w") as xbar_file: - xbar_file.write(laddr_value) - return True + # else: + self.log.trace("Component not found in updateable components: {}" + .format(component_name)) + return {} ########################################################################## # Mboard Sensors @@ -823,86 +802,44 @@ class PeriphManagerBase(object): ####################################################################### # Transport API ####################################################################### - def request_xport( - self, - dst_address, - suggested_src_address, - xport_type, - ): - """ - When setting up a CHDR connection, this is the first call to be - made. This function will return a list of dictionaries, each - describing a way to open an CHDR connection. The list of dictionaries - is sorted by preference, meaning that the caller should use the first - option, if possible. - All transports requested are bidirectional. - - The callee must maintain a lock on the available CHDR xports. After - calling request_xport(), the caller needs to pick one of the - dictionaries, possibly amend data (e.g., if the connection is an - Ethernet connection, then we need to know the source port, but more - details on that in commit_xport()'s documentation). - One way to implement a lock is to simply lock a mutex here and - unlock it in commit_xport(), even though there are probably more - nuanced solutions. + def get_chdr_link_types(self): + """ + Return a list of ways how the UHD session can connect to this device to + initiate CHDR traffic. - Arguments: - dst_sid -- The destination part of the connection, i.e., which - RFNoC block are we connecting to. Example: 0x0230 - suggested_src_sid -- The source part of the connection, i.e., - what's the source address of packets going to - the destination at dst_sid. This is a - suggestion, MPM can override this. Example: - 0x0001. - xport_type -- One of the following strings: CTRL, ASYNC_MSG, - TX_DATA, RX_DATA. See also xports_type_t in UHD. - - The return value is a list of dictionaries. Every dictionary has - the following key/value pairs: - - type: Type of transport, e.g., "UDP", "liberio". - - ipv4 (UDP only): IPv4 address to connect to. - - port (UDP only): IP port to connect to. - - send_sid: String version of the SID used for this transport. This is - the definitive version of the SID, the suggested_src_address - can be ignored at this point. - - allocation: This is an integer value which represents a score of - how much bandwidth is used. Note: Currently does not - have any unit, is just a counter. Higher numbers mean - higher utilization. RX means device to UHD, for - example, committing an RX streamer would increase this - value. - This key is optional, MPM does not have to provide it. - If the allocation affects the preference, it will be - factored into the order of the results, meaning the - caller does not strictly have to check its value even if - the transport option with the smallest allocation is - preferred. - - Note: The dictionary may include other keys which should be ignored, - or at the very least, kept intact. commit_xport() might be requiring - them. - """ - raise NotImplementedError("request_xport() not implemented.") - - def commit_xport(self, xport_info): - """ - When setting up a CHDR connection, this is the second call to be - made. + The return value is a list of strings. Every string is a key for a + transport type. Values include: + - "udp": Means this device can be reached via UDP + - "liberio": Means this device can be reached via Liberio (local DMA) - Arguments: - xport_info -- A dictionary (string -> string). The dictionary must - have been originally created by request_xport(), but - additional key/value pairs need to be added. + The list is filtered based on what the device knows about where the UHD + session is. For example, on an N310, it will only either return "UDP" + or "Liberio", depending on if we're remotely launching UHD, or locally. - All transports need to also provide: - - rx_mtu: In bytes, the max number of bytes going from device to UHD - - tx_mtu: In bytes, the max number of bytes going from UHD to device + In order to get further information about how to connect to the device, + the keys returned from this function can be used with + get_chdr_link_options(). + """ + raise NotImplementedError("get_chdr_link_types() not implemented.") - UDP transports need to also provide: - - src_ipv4: IPv4 address the connection is coming from. - - src_port: IP port the connection is coming from. + def get_chdr_link_options(self, xport_type): """ - raise NotImplementedError("commit_xport() not implemented.") + Returns a list of dictionaries. Every dictionary contains information + about one way to connect to this device in order to initiate CHDR + traffic. + + The interpretation of the return value is very highly dependant on the + transport type (xport_type). + For UDP, the every entry of the list has the following keys: + - ipv4 (IP Address) + - port (UDP port) + - link_rate (bps of the link, e.g. 10e9 for 10GigE) + + For Liberio, every entry has the following keys: + - tx_dev: TX device (/dev/tx-dma*) + - rx_dev: RX device (/dev/rx-dma*) + """ + raise NotImplementedError("get_chdr_link_options() not implemented.") ####################################################################### # Claimer API @@ -927,3 +864,52 @@ class PeriphManagerBase(object): """ self.log.debug("Device was unclaimed. No actions defined.") + ####################################################################### + # Timekeeper API + ####################################################################### + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + raise NotImplementedError("get_num_timekeepers() not implemented.") + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + raise NotImplementedError( + "get_ticks_now({}, {}) not implemented.".format(tk_idx, last_pps)) + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + raise NotImplementedError( + "set_ticks_last_pps({}, {}, {}) not implemented." + .format(tk_idx, ticks, next_pps)) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + raise NotImplementedError( + "set_tick_period({}) not implemented.".format(tk_idx, period_ns)) + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + raise NotImplementedError("get_clocks() not implemented.") diff --git a/mpm/python/usrp_mpm/periph_manager/e31x.py b/mpm/python/usrp_mpm/periph_manager/e31x.py index 722f8d7a9..e93cbef25 100644 --- a/mpm/python/usrp_mpm/periph_manager/e31x.py +++ b/mpm/python/usrp_mpm/periph_manager/e31x.py @@ -30,7 +30,7 @@ from usrp_mpm import e31x_legacy_eeprom E310_DEFAULT_CLOCK_SOURCE = 'internal' E310_DEFAULT_TIME_SOURCE = 'internal' E310_DEFAULT_ENABLE_FPGPIO = True -E310_FPGA_COMPAT = (1,0) +E310_FPGA_COMPAT = (4,0) E310_DBOARD_SLOT_IDX = 0 ############################################################################### @@ -39,9 +39,7 @@ E310_DBOARD_SLOT_IDX = 0 class E310XportMgrLiberio(XportMgrLiberio): " E310-specific Liberio configuration " - max_chan = 4 - xbar_dev = "/dev/crossbar0" - xbar_port = 0 + max_chan = 5 ############################################################################### # Main Class @@ -178,7 +176,6 @@ class e31x(ZynqComponents, PeriphManagerBase): self._tear_down = False self._clock_source = None self._time_source = None - self._available_endpoints = list(range(256)) self.dboard = self.dboards[E310_DBOARD_SLOT_IDX] try: self._init_peripherals(self.args_cached) @@ -277,9 +274,6 @@ class e31x(ZynqComponents, PeriphManagerBase): self.mboard_regs_control.get_build_timestamp() self._check_fpga_compat() self._update_fpga_type() - self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport() - self.log.debug("crossbar base port: {}".format(self.crossbar_base_port)) - # Init clocking self._init_ref_clock_and_time(args) # Init CHDR transports @@ -443,7 +437,6 @@ class e31x(ZynqComponents, PeriphManagerBase): for xport_mgr in itervalues(self._xport_mgrs): xport_mgr.deinit() self.log.trace("Resetting SID pool...") - self._available_endpoints = list(range(256)) if not self._do_not_reload: self.tear_down() # Reset back to value from _default_args (mpm.conf) @@ -480,56 +473,22 @@ class e31x(ZynqComponents, PeriphManagerBase): self.log.trace("Found idle overlay: %s", idle_overlay) return is_idle + ########################################################################### # Transport API ########################################################################### - def request_xport( - self, - dst_address, - suggested_src_address, - xport_type - ): - """ - See PeriphManagerBase.request_xport() for docs. - """ - # Try suggested address first, then just pick the first available one: - src_address = suggested_src_address - if src_address not in self._available_endpoints: - if not self._available_endpoints: - raise RuntimeError( - "Depleted pool of SID endpoints for this device!") - else: - src_address = self._available_endpoints[0] - sid = SID(src_address << 16 | dst_address) - # Note: This SID may change its source address! - self.log.trace( - "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ - "operating on temporary SID: %s", - dst_address, suggested_src_address, str(xport_type), str(sid)) - assert self.mboard_info['rpc_connection'] in ('local') - if self.mboard_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].request_xport( - sid, - xport_type, - ) - - def commit_xport(self, xport_info): + def get_chdr_link_types(self): """ - See PeriphManagerBase.commit_xport() for docs. + See PeriphManagerBase.get_chdr_link_types() for docs. + """ + return ['liberio'] - Reminder: All connections are incoming, i.e. "send" or "TX" means - remote device to local device, and "receive" or "RX" means this local - device to remote device. "Remote device" can be, for example, a UHD - session. + def get_chdr_link_options(self, xport_type): + """ + See PeriphManagerBase.get_chdr_link_options() for docs. """ - ## Go, go, go - assert self.mboard_info['rpc_connection'] in ('local') - sid = SID(xport_info['send_sid']) - self._available_endpoints.remove(sid.src_ep) - self.log.debug("Committing transport for SID %s, xport info: %s", - str(sid), str(xport_info)) - if self.mboard_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) + if xport_type == 'liberio': + return self._xport_mgrs['liberio'].get_chdr_link_options() ########################################################################### # Device info @@ -550,6 +509,39 @@ class e31x(ZynqComponents, PeriphManagerBase): }) return device_info + def set_device_id(self, device_id): + """ + Sets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + self.log.debug("Setting device ID to `{}'".format(device_id)) + self.mboard_regs_control.set_device_id(device_id) + + def get_device_id(self): + """ + Gets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + return self.mboard_regs_control.get_device_id() + + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + proto_ver = self.mboard_regs_control.get_proto_ver() + self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) + return proto_ver + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + chdr_width = self.mboard_regs_control.get_chdr_width() + self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) + return chdr_width + ########################################################################### # Clock/Time API ########################################################################### @@ -742,3 +734,59 @@ class e31x(ZynqComponents, PeriphManagerBase): fpga_type = self.mboard_regs_control.get_fpga_type() self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type)) self.updateable_components['fpga']['type'] = fpga_type + + ####################################################################### + # Timekeeper API + ####################################################################### + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + return self.mboard_regs_control.get_num_timekeepers() + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + return [ + { + 'name': 'radio_clk', + 'freq': str(self.dboard.get_master_clock_rate()), + 'mutable': 'true' + }, + { + 'name': 'bus_clk', + 'freq': str(100e6), + } + ] diff --git a/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py index 0b166e5bc..330cd830e 100644 --- a/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/e31x_periphs.py @@ -33,23 +33,40 @@ class MboardRegsControl(object): Control the FPGA Motherboard registers """ # Motherboard registers - MB_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_GPIO_MASTER = 0x0030 - MB_GPIO_RADIO_SRC = 0x0034 - MB_GPS_CTRL = 0x0038 - MB_GPS_STATUS = 0x003C - MB_DBOARD_CTRL = 0x0040 - MB_DBOARD_STATUS = 0x0044 - MB_XBAR_BASEPORT = 0x0048 + MB_COMPAT_NUM = 0x0000 + MB_DATESTAMP = 0x0004 + MB_GIT_HASH = 0x0008 + MB_SCRATCH = 0x000C + MB_DEVICE_ID = 0x0010 + MB_RFNOC_INFO = 0x0014 + MB_CLOCK_CTRL = 0x0018 + MB_XADC_RB = 0x001C + MB_BUS_CLK_RATE = 0x0020 + MB_BUS_COUNTER = 0x0024 + MB_SFP_PORT_INFO = 0x0028 + MB_GPIO_CTRL = 0x002C + MB_GPIO_MASTER = 0x0030 + MB_GPIO_RADIO_SRC = 0x0034 + MB_GPS_CTRL = 0x0038 + MB_GPS_STATUS = 0x003C + MB_DBOARD_CTRL = 0x0040 + MB_DBOARD_STATUS = 0x0044 + MB_NUM_TIMEKEEPERS = 0x0048 + # Timekeeper registers + MB_TIME_NOW_LO = 0x1000 + MB_TIME_NOW_HI = 0x1004 + MB_TIME_EVENT_LO = 0x1008 + MB_TIME_EVENT_HI = 0x100C + MB_TIME_CTRL = 0x1010 + MB_TIME_LAST_PPS_LO = 0x1014 + MB_TIME_LAST_PPS_HI = 0x1018 + MB_TIME_BASE_PERIOD_LO = 0x101C + MB_TIME_BASE_PERIOD_HI = 0x1020 + MB_TIMEKEEPER_OFFSET = 12 + + # Bitfield locations for the MB_RFNOC_INFO register. + MB_RFNOC_INFO_PROTO_VER = 0 + MB_RFNOC_INFO_CHDR_WIDTH = 16 # Bitfield locations for the MB_CLOCK_CTRL register. MB_CLOCK_CTRL_PPS_SEL_INT = 0 @@ -100,6 +117,26 @@ class MboardRegsControl(object): major = (compat_number>>16) & 0xff return (major, minor) + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER + self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) + return proto_ver + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH + self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) + return chdr_width + def set_fp_gpio_master(self, value): """set driver for front panel GPIO Arguments: @@ -136,6 +173,24 @@ class MboardRegsControl(object): with self.regs: return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff + def set_device_id(self, device_id): + """ + Set device ID + """ + with self.regs: + self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) + return self.poke32(self.MB_DEVICE_ID, device_id) + + def get_device_id(self): + """ + Get device ID + """ + with self.regs: + regs_val = self.peek32(self.MB_DEVICE_ID) + device_id = regs_val & 0x0000ffff + self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) + return device_id + def get_build_timestamp(self): """ Returns the build date/time for the FPGA image. @@ -206,6 +261,71 @@ class MboardRegsControl(object): else: assert False, "Cannot set to invalid clock source: {}".format(clock_source) + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + with self.regs: + return self.peek32(self.MB_NUM_TIMEKEEPERS) + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + addr_lo = \ + (self.MB_TIME_LAST_PPS_LO if last_pps else self.MB_TIME_NOW_LO) + \ + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + with self.regs: + time_lo = self.peek32(addr_lo) + time_hi = self.peek32(addr_hi) + return time_hi << 32 | time_lo + + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + addr_lo = \ + self.MB_TIME_EVENT_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + addr_ctrl = \ + self.MB_TIME_CTRL + tk_idx * self.MB_TIMEKEEPER_OFFSET + time_lo = ticks & 0xFFFFFFFF + time_hi = (ticks > 32) & 0xFFFFFFFF + time_ctrl = 0x2 if next_pps else 0x1 + self.log.trace("Setting time on timekeeper %d to %d %s", tk_idx, ticks, + ("on next pps" if next_pps else "now")) + with self.regs: + self.poke32(addr_lo, time_lo) + self.poke32(addr_hi, time_hi) + self.poke32(addr_ctrl, time_ctrl) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + addr_lo = self.MB_TIME_BASE_PERIOD_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + period_lo = period_ns & 0xFFFFFFFF + period_hi = (period_ns > 32) & 0xFFFFFFFF + with self.regs: + self.poke32(addr_lo, period_lo) + self.poke32(addr_hi, period_hi) + def get_fpga_type(self): """ Reads the type of the FPGA image currently loaded @@ -248,7 +368,8 @@ class MboardRegsControl(object): reg_val = self.peek32(self.MB_DBOARD_CTRL) if channel_mode == "MIMO": reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO) - self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T")) + self.log.trace("Setting channel mode in AD9361 interface: %s", + "2R2T" if channel_mode == 2 else "1R1T") else: # Warn if user tries to set either tx0/tx1 in mimo mode # as both will be set automatically @@ -269,7 +390,7 @@ class MboardRegsControl(object): """ mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK with self.regs: - reg_val = self.peek32(self.MB_DBOARD_STATUS) + reg_val = self.peek32(self.MB_DBOARD_STATUS) locked = (reg_val & mask) > 0 if not locked: self.log.warning("TX RF PLL reporting unlocked. ") @@ -283,15 +404,10 @@ class MboardRegsControl(object): """ mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK with self.regs: - reg_val = self.peek32(self.MB_DBOARD_STATUS) + reg_val = self.peek32(self.MB_DBOARD_STATUS) locked = (reg_val & mask) > 0 if not locked: self.log.warning("RX RF PLL reporting unlocked. ") else: self.log.trace("RX RF PLL locked") return locked - - def get_xbar_baseport(self): - "Get the RFNoC crossbar base port" - with self.regs: - return self.peek32(self.MB_XBAR_BASEPORT) diff --git a/mpm/python/usrp_mpm/periph_manager/e320.py b/mpm/python/usrp_mpm/periph_manager/e320.py index f76e6ac3e..04999e505 100644 --- a/mpm/python/usrp_mpm/periph_manager/e320.py +++ b/mpm/python/usrp_mpm/periph_manager/e320.py @@ -32,7 +32,7 @@ E320_DEFAULT_CLOCK_SOURCE = 'internal' E320_DEFAULT_TIME_SOURCE = 'internal' E320_DEFAULT_ENABLE_GPS = True E320_DEFAULT_ENABLE_FPGPIO = True -E320_FPGA_COMPAT = (3, 1) +E320_FPGA_COMPAT = (4, 0) E320_MONITOR_THREAD_INTERVAL = 1.0 # seconds E320_DBOARD_SLOT_IDX = 0 @@ -42,21 +42,15 @@ E320_DBOARD_SLOT_IDX = 0 ############################################################################### class E320XportMgrUDP(XportMgrUDP): "E320-specific UDP configuration" - xbar_dev = "/dev/crossbar0" iface_config = { 'sfp0': { 'label': 'misc-enet-regs', - 'xbar': 0, - 'xbar_port': 0, - 'ctrl_src_addr': 0, } } class E320XportMgrLiberio(XportMgrLiberio): " E320-specific Liberio configuration " max_chan = 6 - xbar_dev = "/dev/crossbar0" - xbar_port = 1 ############################################################################### # Main Class @@ -139,7 +133,6 @@ class e320(ZynqComponents, PeriphManagerBase): self._ext_clock_freq = E320_DEFAULT_EXT_CLOCK_FREQ self._clock_source = None self._time_source = None - self._available_endpoints = list(range(256)) self._gpsd = None self.dboard = self.dboards[E320_DBOARD_SLOT_IDX] from functools import partial @@ -266,7 +259,6 @@ class e320(ZynqComponents, PeriphManagerBase): self.mboard_regs_control.get_build_timestamp() self._check_fpga_compat() self._update_fpga_type() - self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport() # Init peripherals self.enable_gps( enable=str2bool( @@ -274,10 +266,7 @@ class e320(ZynqComponents, PeriphManagerBase): ) ) self.enable_fp_gpio( - enable=args.get( - 'enable_fp_gpio', - E320_DEFAULT_ENABLE_FPGPIO - ) + enable=args.get('enable_fp_gpio', E320_DEFAULT_ENABLE_FPGPIO) ) # Init clocking self._init_ref_clock_and_time(args) @@ -285,8 +274,8 @@ class e320(ZynqComponents, PeriphManagerBase): self._init_gps_sensors() # Init CHDR transports self._xport_mgrs = { - 'udp': E320XportMgrUDP(self.log.getChild('UDP'), args), - 'liberio': E320XportMgrLiberio(self.log.getChild('liberio')), + 'udp': E320XportMgrUDP(self.log, args), + 'liberio': E320XportMgrLiberio(self.log), } # Spawn status monitoring thread self.log.trace("Spawning status monitor thread...") @@ -347,8 +336,6 @@ class e320(ZynqComponents, PeriphManagerBase): super(e320, self).deinit() for xport_mgr in itervalues(self._xport_mgrs): xport_mgr.deinit() - self.log.trace("Resetting SID pool...") - self._available_endpoints = list(range(256)) def tear_down(self): """ @@ -372,61 +359,37 @@ class e320(ZynqComponents, PeriphManagerBase): ########################################################################### # Transport API ########################################################################### - def request_xport( - self, - dst_address, - suggested_src_address, - xport_type - ): - """ - See PeriphManagerBase.request_xport() for docs. - """ - # Try suggested address first, then just pick the first available one: - src_address = suggested_src_address - if src_address not in self._available_endpoints: - if not self._available_endpoints: - raise RuntimeError( - "Depleted pool of SID endpoints for this device!") - else: - src_address = self._available_endpoints[0] - sid = SID(src_address << 16 | dst_address) - # Note: This SID may change its source address! - self.log.trace( - "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ - "operating on temporary SID: %s", - dst_address, suggested_src_address, str(xport_type), str(sid)) - # FIXME token! + def get_chdr_link_types(self): + """ + This will only ever return a single item (udp or liberio). + """ assert self.mboard_info['rpc_connection'] in ('remote', 'local') if self.mboard_info['rpc_connection'] == 'remote': - return self._xport_mgrs['udp'].request_xport( - sid, - xport_type, - ) - elif self.mboard_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].request_xport( - sid, - xport_type, - ) + return ["udp"] + # else: + return ["liberio"] - def commit_xport(self, xport_info): + def get_chdr_link_options(self, xport_type): """ - See PeriphManagerBase.commit_xport() for docs. + Returns a list of dictionaries. Every dictionary contains information + about one way to connect to this device in order to initiate CHDR + traffic. + + The interpretation of the return value is very highly dependant on the + transport type (xport_type). + For UDP, the every entry of the list has the following keys: + - ipv4 (IP Address) + - port (UDP port) + - link_rate (bps of the link, e.g. 10e9 for 10GigE) - Reminder: All connections are incoming, i.e. "send" or "TX" means - remote device to local device, and "receive" or "RX" means this local - device to remote device. "Remote device" can be, for example, a UHD - session. + For Liberio, every entry has the following keys: + - tx_dev: TX device (/dev/tx-dma*) + - rx_dev: RX device (/dev/rx-dma*) """ - ## Go, go, go - assert self.mboard_info['rpc_connection'] in ('remote', 'local') - sid = SID(xport_info['send_sid']) - self._available_endpoints.remove(sid.src_ep) - self.log.debug("Committing transport for SID %s, xport info: %s", - str(sid), str(xport_info)) - if self.mboard_info['rpc_connection'] == 'remote': - return self._xport_mgrs['udp'].commit_xport(sid, xport_info) - elif self.mboard_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) + if xport_type not in self._xport_mgrs: + self.log.warning("Can't get link options for unknown link type: `{}'.") + return [] + return self._xport_mgrs[xport_type].get_chdr_link_options() ########################################################################### # Device info @@ -443,10 +406,43 @@ class e320(ZynqComponents, PeriphManagerBase): *self.mboard_regs_control.get_compat_number()), 'fpga_version_hash': "{:x}.{}".format( *self.mboard_regs_control.get_git_hash()), - 'fpga': self.updateable_components.get('fpga', {}).get('type',""), + 'fpga': self.updateable_components.get('fpga', {}).get('type', ""), }) return device_info + def set_device_id(self, device_id): + """ + Sets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + self.log.debug("Setting device ID to `{}'".format(device_id)) + self.mboard_regs_control.set_device_id(device_id) + + def get_device_id(self): + """ + Gets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + return self.mboard_regs_control.get_device_id() + + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + proto_ver = self.mboard_regs_control.get_proto_ver() + self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) + return proto_ver + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + chdr_width = self.mboard_regs_control.get_chdr_width() + self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) + return chdr_width + ########################################################################### # Clock/Time API ########################################################################### @@ -637,7 +633,7 @@ class e320(ZynqComponents, PeriphManagerBase): except ValueError: self.log.warning("Error when converting temperature value") except KeyError: - self.log.warning("Can't read temp on thermal_zone".format(sensor)) + self.log.warning("Can't read temp on thermal_zone {}".format(sensor)) return { 'name': sensor_name, 'type': 'REALNUM', @@ -753,3 +749,59 @@ class e320(ZynqComponents, PeriphManagerBase): fpga_type = self.mboard_regs_control.get_fpga_type() self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type)) self.updateable_components['fpga']['type'] = fpga_type + + ####################################################################### + # Timekeeper API + ####################################################################### + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + return self.mboard_regs_control.get_num_timekeepers() + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + return [ + { + 'name': 'radio_clk', + 'freq': str(self.dboard.get_master_clock_rate()), + 'mutable': 'true' + }, + { + 'name': 'bus_clk', + 'freq': str(200e6), + } + ] diff --git a/mpm/python/usrp_mpm/periph_manager/e320_periphs.py b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py index 0cf7f59c6..cad5c39ad 100644 --- a/mpm/python/usrp_mpm/periph_manager/e320_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py @@ -48,25 +48,40 @@ class MboardRegsControl(object): Control the FPGA Motherboard registers """ # Motherboard registers - MB_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_SFP_PORT_INFO = 0x0028 - MB_GPIO_CTRL = 0x002C - MB_GPIO_MASTER = 0x0030 - MB_GPIO_RADIO_SRC = 0x0034 - MB_GPS_CTRL = 0x0038 - MB_GPS_STATUS = 0x003C - MB_DBOARD_CTRL = 0x0040 - MB_DBOARD_STATUS = 0x0044 - MB_XBAR_BASEPORT = 0x0048 + MB_COMPAT_NUM = 0x0000 + MB_DATESTAMP = 0x0004 + MB_GIT_HASH = 0x0008 + MB_SCRATCH = 0x000C + MB_DEVICE_ID = 0x0010 + MB_RFNOC_INFO = 0x0014 + MB_CLOCK_CTRL = 0x0018 + MB_XADC_RB = 0x001C + MB_BUS_CLK_RATE = 0x0020 + MB_BUS_COUNTER = 0x0024 + MB_SFP_PORT_INFO = 0x0028 + MB_GPIO_CTRL = 0x002C + MB_GPIO_MASTER = 0x0030 + MB_GPIO_RADIO_SRC = 0x0034 + MB_GPS_CTRL = 0x0038 + MB_GPS_STATUS = 0x003C + MB_DBOARD_CTRL = 0x0040 + MB_DBOARD_STATUS = 0x0044 + MB_NUM_TIMEKEEPERS = 0x0048 + # Timekeeper registers + MB_TIME_NOW_LO = 0x1000 + MB_TIME_NOW_HI = 0x1004 + MB_TIME_EVENT_LO = 0x1008 + MB_TIME_EVENT_HI = 0x100C + MB_TIME_CTRL = 0x1010 + MB_TIME_LAST_PPS_LO = 0x1014 + MB_TIME_LAST_PPS_HI = 0x1018 + MB_TIME_BASE_PERIOD_LO = 0x101C + MB_TIME_BASE_PERIOD_HI = 0x1020 + MB_TIMEKEEPER_OFFSET = 12 + + # Bitfield locations for the MB_RFNOC_INFO register. + MB_RFNOC_INFO_PROTO_VER = 0 + MB_RFNOC_INFO_CHDR_WIDTH = 16 # Bitfield locations for the MB_CLOCK_CTRL register. MB_CLOCK_CTRL_PPS_SEL_INT = 0 @@ -122,6 +137,44 @@ class MboardRegsControl(object): major = (compat_number>>16) & 0xff return (major, minor) + def set_device_id(self, device_id): + """ + Set device ID + """ + with self.regs: + self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) + return self.poke32(self.MB_DEVICE_ID, device_id) + + def get_device_id(self): + """ + Get device ID + """ + with self.regs: + reg_val = self.peek32(self.MB_DEVICE_ID) + device_id = reg_val & 0x0000ffff + self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) + return device_id + + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER + self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) + return proto_ver; + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH + self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) + return chdr_width + def enable_fp_gpio(self, enable): """ Enable front panel GPIO buffers and power supply and set voltage 3.3 V @@ -372,7 +425,8 @@ class MboardRegsControl(object): reg_val = self.peek32(self.MB_DBOARD_CTRL) if channel_mode == "MIMO": reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO) - self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T")) + self.log.trace("Setting channel mode in AD9361 interface: %s", + "2R2T" if channel_mode == 2 else "1R1T") else: # Warn if user tries to set either tx0/tx1 in mimo mode # as both will be set automatically @@ -393,7 +447,7 @@ class MboardRegsControl(object): """ mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK with self.regs: - reg_val = self.peek32(self.MB_DBOARD_STATUS) + reg_val = self.peek32(self.MB_DBOARD_STATUS) locked = (reg_val & mask) > 0 if not locked: self.log.warning("TX RF PLL reporting unlocked. ") @@ -407,7 +461,7 @@ class MboardRegsControl(object): """ mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK with self.regs: - reg_val = self.peek32(self.MB_DBOARD_STATUS) + reg_val = self.peek32(self.MB_DBOARD_STATUS) locked = (reg_val & mask) > 0 if not locked: self.log.warning("RX RF PLL reporting unlocked. ") @@ -415,7 +469,67 @@ class MboardRegsControl(object): self.log.trace("RX RF PLL locked") return locked - def get_xbar_baseport(self): - "Get the RFNoC crossbar base port" + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + with self.regs: + return self.peek32(self.MB_NUM_TIMEKEEPERS) + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + addr_lo = \ + (self.MB_TIME_LAST_PPS_LO if last_pps else self.MB_TIME_NOW_LO) + \ + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + with self.regs: + time_lo = self.peek32(addr_lo) + time_hi = self.peek32(addr_hi) + return time_hi << 32 | time_lo + + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + addr_lo = \ + self.MB_TIME_EVENT_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + addr_ctrl = \ + self.MB_TIME_CTRL + tk_idx * self.MB_TIMEKEEPER_OFFSET + time_lo = ticks & 0xFFFFFFFF + time_hi = (ticks > 32) & 0xFFFFFFFF + time_ctrl = 0x2 if next_pps else 0x1 + self.log.trace("Setting time on timekeeper %d to %d %s", tk_idx, ticks, + ("on next pps" if next_pps else "now")) + with self.regs: + self.poke32(addr_lo, time_lo) + self.poke32(addr_hi, time_hi) + self.poke32(addr_ctrl, time_ctrl) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + addr_lo = self.MB_TIME_BASE_PERIOD_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET + addr_hi = addr_lo + 4 + period_lo = period_ns & 0xFFFFFFFF + period_hi = (period_ns > 32) & 0xFFFFFFFF with self.regs: - return self.peek32(self.MB_XBAR_BASEPORT) + self.poke32(addr_lo, period_lo) + self.poke32(addr_hi, period_hi) diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py index 2695dbfba..8d5f83569 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py @@ -41,7 +41,7 @@ N3XX_DEFAULT_ENABLE_PPS_EXPORT = True N32X_DEFAULT_QSFP_RATE_PRESET = 'Ethernet' N32X_DEFAULT_QSFP_DRIVER_PRESET = 'Optical' N32X_QSFP_I2C_LABEL = 'qsfp-i2c' -N3XX_FPGA_COMPAT = (5, 3) +N3XX_FPGA_COMPAT = (6, 0) N3XX_MONITOR_THREAD_INTERVAL = 1.0 # seconds # Import daughterboard PIDs from their respective classes @@ -52,39 +52,24 @@ RHODIUM_PID = Rhodium.pids[0] ############################################################################### # Transport managers ############################################################################### +# pylint: disable=too-few-public-methods class N3xxXportMgrUDP(XportMgrUDP): " N3xx-specific UDP configuration " - xbar_dev = "/dev/crossbar0" iface_config = { 'bridge0': { 'label': 'misc-enet-regs0', - 'xbar': 0, - 'xbar_port': 0, - 'ctrl_src_addr': 0, }, 'sfp0': { 'label': 'misc-enet-regs0', - 'xbar': 0, - 'xbar_port': 0, - 'ctrl_src_addr': 0, }, 'sfp1': { 'label': 'misc-enet-regs1', - 'xbar': 0, - 'xbar_port': 1, - 'ctrl_src_addr': 1, }, 'eth1': { 'label': 'misc-enet-regs0', - 'xbar': 0, - 'xbar_port': 0, - 'ctrl_src_addr': 0, }, 'eth2': { 'label': 'misc-enet-regs1', - 'xbar': 0, - 'xbar_port': 1, - 'ctrl_src_addr': 1, }, } bridges = {'bridge0': ['sfp0', 'sfp1', 'bridge0']} @@ -92,8 +77,7 @@ class N3xxXportMgrUDP(XportMgrUDP): class N3xxXportMgrLiberio(XportMgrLiberio): " N3xx-specific Liberio configuration " max_chan = 10 - xbar_dev = "/dev/crossbar0" - xbar_port = 2 +# pylint: enable=too-few-public-methods ############################################################################### # Main Class @@ -216,7 +200,6 @@ class n3xx(ZynqComponents, PeriphManagerBase): self._ext_clock_freq = None self._clock_source = None self._time_source = None - self._available_endpoints = list(range(256)) self._bp_leds = None self._gpsd = None self._qsfp_retimer = None @@ -352,7 +335,6 @@ class n3xx(ZynqComponents, PeriphManagerBase): self.mboard_regs_control.get_git_hash() self.mboard_regs_control.get_build_timestamp() self._check_fpga_compat() - self.crossbar_base_port = self.mboard_regs_control.get_xbar_baseport() # Init clocking self.enable_ref_clock(enable=True) self._ext_clock_freq = None @@ -463,8 +445,6 @@ class n3xx(ZynqComponents, PeriphManagerBase): super(n3xx, self).deinit() for xport_mgr in itervalues(self._xport_mgrs): xport_mgr.deinit() - self.log.trace("Resetting SID pool...") - self._available_endpoints = list(range(256)) def tear_down(self): """ @@ -489,66 +469,37 @@ class n3xx(ZynqComponents, PeriphManagerBase): ########################################################################### # Transport API ########################################################################### - def request_xport( - self, - dst_address, - suggested_src_address, - xport_type - ): - """ - See PeriphManagerBase.request_xport() for docs. - """ - # Try suggested address first, then just pick the first available one: - src_address = suggested_src_address - if src_address not in self._available_endpoints: - if len(self._available_endpoints) == 0: - raise RuntimeError( - "Depleted pool of SID endpoints for this device!") - else: - src_address = self._available_endpoints[0] - sid = SID(src_address << 16 | dst_address) - # Note: This SID may change its source address! - self.log.trace( - "request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \ - "operating on temporary SID: %s", - dst_address, suggested_src_address, str(xport_type), str(sid)) - # FIXME token! - assert self.device_info['rpc_connection'] in ('remote', 'local') - if self.device_info['rpc_connection'] == 'remote': - if self.device_info['product'] == 'n320': - alloc_limit = 1 - else: - alloc_limit = 2 - return self._xport_mgrs['udp'].request_xport( - sid, - xport_type, - alloc_limit - ) - elif self.device_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].request_xport( - sid, - xport_type, - ) + def get_chdr_link_types(self): + """ + This will only ever return a single item (udp or liberio). + """ + assert self.mboard_info['rpc_connection'] in ('remote', 'local') + if self.mboard_info['rpc_connection'] == 'remote': + return ["udp"] + # else: + return ["liberio"] - def commit_xport(self, xport_info): + def get_chdr_link_options(self, xport_type): """ - See PeriphManagerBase.commit_xport() for docs. + Returns a list of dictionaries. Every dictionary contains information + about one way to connect to this device in order to initiate CHDR + traffic. + + The interpretation of the return value is very highly dependant on the + transport type (xport_type). + For UDP, the every entry of the list has the following keys: + - ipv4 (IP Address) + - port (UDP port) + - link_rate (bps of the link, e.g. 10e9 for 10GigE) - Reminder: All connections are incoming, i.e. "send" or "TX" means - remote device to local device, and "receive" or "RX" means this local - device to remote device. "Remote device" can be, for example, a UHD - session. + For Liberio, every entry has the following keys: + - tx_dev: TX device (/dev/tx-dma*) + - rx_dev: RX device (/dev/rx-dma*) """ - ## Go, go, go - assert self.device_info['rpc_connection'] in ('remote', 'local') - sid = SID(xport_info['send_sid']) - self._available_endpoints.remove(sid.src_ep) - self.log.debug("Committing transport for SID %s, xport info: %s", - str(sid), str(xport_info)) - if self.device_info['rpc_connection'] == 'remote': - return self._xport_mgrs['udp'].commit_xport(sid, xport_info) - elif self.device_info['rpc_connection'] == 'local': - return self._xport_mgrs['liberio'].commit_xport(sid, xport_info) + if xport_type not in self._xport_mgrs: + self.log.warning("Can't get link options for unknown link type: `{}'.") + return [] + return self._xport_mgrs[xport_type].get_chdr_link_options() ########################################################################### # Device info @@ -569,6 +520,39 @@ class n3xx(ZynqComponents, PeriphManagerBase): }) return device_info + def set_device_id(self, device_id): + """ + Sets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + self.log.debug("Setting device ID to `{}'".format(device_id)) + self.mboard_regs_control.set_device_id(device_id) + + def get_device_id(self): + """ + Gets the device ID for this motherboard. + The device ID is used to identify the RFNoC components associated with + this motherboard. + """ + return self.mboard_regs_control.get_device_id() + + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + proto_ver = self.mboard_regs_control.get_proto_ver() + self.log.debug("RFNoC protocol version supported by this device is {}".format(proto_ver)) + return proto_ver + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + chdr_width = self.mboard_regs_control.get_chdr_width() + self.log.debug("CHDR width supported by the device is {}".format(chdr_width)) + return chdr_width + ########################################################################### # Clock/Time API ########################################################################### @@ -1065,3 +1049,60 @@ class n3xx(ZynqComponents, PeriphManagerBase): if self._bp_leds is not None: # Turn off LINK self._bp_leds.set(self._bp_leds.LED_LINK, 0) + + ####################################################################### + # Timekeeper API + ####################################################################### + def get_num_timekeepers(self): + """ + Return the number of timekeepers + """ + return self.mboard_regs_control.get_num_timekeepers() + + def get_timekeeper_time(self, tk_idx, last_pps): + """ + Get the time in ticks + + Arguments: + tk_idx: Index of timekeeper + next_pps: If True, get time at last PPS. Otherwise, get time now. + """ + return self.mboard_regs_control.get_timekeeper_time(tk_idx, last_pps) + + def set_timekeeper_time(self, tk_idx, ticks, next_pps): + """ + Set the time in ticks + + Arguments: + tk_idx: Index of timekeeper + ticks: Time in ticks + next_pps: If True, set time at next PPS. Otherwise, set time now. + """ + self.mboard_regs_control.set_timekeeper_time(tk_idx, ticks, next_pps) + + def set_tick_period(self, tk_idx, period_ns): + """ + Set the time per tick in nanoseconds (tick period) + + Arguments: + tk_idx: Index of timekeeper + period_ns: Period in nanoseconds + """ + self.mboard_regs_control.set_tick_period(tk_idx, period_ns) + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + return [ + { + 'name': 'radio_clk', + 'freq': str(self.dboards[0].get_master_clock_rate()), + 'mutable': 'true' + }, + { + 'name': 'bus_clk', + 'freq': str(200e6), + } + ] + diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py index cb6c237c2..fe005a716 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py @@ -165,12 +165,12 @@ class MboardRegsControl(object): Control the FPGA Motherboard registers """ # Motherboard registers - M_COMPAT_NUM = 0x0000 + MB_COMPAT_NUM = 0x0000 MB_DATESTAMP = 0x0004 MB_GIT_HASH = 0x0008 MB_SCRATCH = 0x000C - MB_NUM_CE = 0x0010 - MB_NUM_IO_CE = 0x0014 + MB_DEVICE_ID = 0x0010 + MB_RFNOC_INFO = 0x0014 MB_CLOCK_CTRL = 0x0018 MB_XADC_RB = 0x001C MB_BUS_CLK_RATE = 0x0020 @@ -179,7 +179,22 @@ class MboardRegsControl(object): MB_SFP1_INFO = 0x002C MB_GPIO_MASTER = 0x0030 MB_GPIO_RADIO_SRC = 0x0034 - MB_XBAR_BASEPORT = 0x0038 + MB_NUM_TIMEKEEPERS = 0x0048 + # Timekeeper registers + MB_TIME_NOW_LO = 0x1000 + MB_TIME_NOW_HI = 0x1004 + MB_TIME_EVENT_LO = 0x1008 + MB_TIME_EVENT_HI = 0x100C + MB_TIME_CTRL = 0x1010 + MB_TIME_LAST_PPS_LO = 0x1014 + MB_TIME_LAST_PPS_HI = 0x1018 + MB_TIME_BASE_PERIOD_LO = 0x101C + MB_TIME_BASE_PERIOD_HI = 0x1020 + MB_TIMEKEEPER_OFFSET = 12 + + # Bitfield locations for the MB_RFNOC_INFO register. + MB_RFNOC_INFO_PROTO_VER = 0 + MB_RFNOC_INFO_CHDR_WIDTH = 16 # Bitfield locations for the MB_CLOCK_CTRL register. MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded! @@ -210,11 +225,49 @@ class MboardRegsControl(object): 2 numbers: (major compat number, minor compat number ) """ with self.regs: - compat_number = self.peek32(self.M_COMPAT_NUM) + compat_number = self.peek32(self.MB_COMPAT_NUM) minor = compat_number & 0xff major = (compat_number>>16) & 0xff return (major, minor) + def set_device_id(self, device_id): + """ + Set device ID + """ + with self.regs: + self.log.trace("Writing MB_DEVICE_ID with 0x{:08X}".format(device_id)) + return self.poke32(self.MB_DEVICE_ID, device_id) + + def get_device_id(self): + """ + Get device ID + """ + with self.regs: + reg_val = self.peek32(self.MB_DEVICE_ID) + device_id = reg_val & 0x0000ffff + self.log.trace("Read MB_DEVICE_ID 0x{:08X}".format(device_id)) + return device_id + + def get_proto_ver(self): + """ + Return RFNoC protocol version + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + proto_ver = (reg_val & 0x0000ffff) >> self.MB_RFNOC_INFO_PROTO_VER + self.log.trace("Read RFNOC_PROTO_VER 0x{:08X}".format(proto_ver)) + return proto_ver; + + def get_chdr_width(self): + """ + Return RFNoC CHDR width + """ + with self.regs: + reg_val = self.peek32(self.MB_RFNOC_INFO) + chdr_width = (reg_val & 0xffff0000) >> self.MB_RFNOC_INFO_CHDR_WIDTH + self.log.trace("Read RFNOC_CHDR_WIDTH 0x{:08X}".format(chdr_width)) + return chdr_width + def set_fp_gpio_master(self, value): """set driver for front panel GPIO Arguments: @@ -416,10 +469,6 @@ class MboardRegsControl(object): .format(sfp0_type, sfp1_type)) return "" - def get_xbar_baseport(self): - "Get the RFNoC crossbar base port" - with self.regs: - return self.peek32(self.MB_XBAR_BASEPORT) class RetimerQSFP(DS125DF410): # (deemphasis, swing) |