diff options
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/sim.py | 47 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/CMakeLists.txt | 2 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/chdr_endpoint.py | 3 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/config.py | 80 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/hardware_presets.py | 10 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/rfnoc_graph.py | 6 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/sim_dboard.py | 34 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/simulator/sim_dboard_catalina.py | 33 |
8 files changed, 149 insertions, 66 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/sim.py b/mpm/python/usrp_mpm/periph_manager/sim.py index 3e761c809..d909bed34 100644 --- a/mpm/python/usrp_mpm/periph_manager/sim.py +++ b/mpm/python/usrp_mpm/periph_manager/sim.py @@ -15,7 +15,7 @@ from usrp_mpm.xports import XportMgrUDP from usrp_mpm.mpmlog import get_logger from usrp_mpm.rpc_server import no_claim from usrp_mpm.periph_manager import PeriphManagerBase -from usrp_mpm.simulator.sim_dboard_catalina import SimulatedCatalinaDboard +from usrp_mpm.simulator.sim_dboard import registry as dboards from usrp_mpm.simulator.chdr_endpoint import ChdrEndpoint from usrp_mpm.simulator.config import Config @@ -70,27 +70,30 @@ class sim(PeriphManagerBase): ######################################################################### # Overridables ######################################################################### - description = "E320-Series Device - SIMULATED" - pids = {0xE320: 'e320'} - - mboard_info = {"type": "e3xx", "product": "e320"} - mboard_max_rev = 7 # RevC mboard_sensor_callback_map = {} ########################################################################### # Ctor and device initialization tasks ########################################################################### def __init__(self, args): - super().__init__() + # Logger is initialized in super().__init__ but we need config values + # before we call that + config_log = get_logger("PeriphConfig") if 'config' in args: config_path = args['config'] - self.log.info("Loading config from {}".format(config_path)) - self.config = Config.from_path(config_path) + config_log.info("Loading config from {}".format(config_path)) + self.config = Config.from_path(config_log, config_path) else: - self.log.warn("No config specified, using default") + config_log.warn("No config specified, using default") self.config = Config.default() self.device_id = 1 + self.description = self.config.hardware.description + self.mboard_info = {"type": self.config.hardware.uhd_device_type, + "product": self.config.hardware.product} + self.pids = {int(self.config.hardware.pid): self.config.hardware.product} + # This uses the description, mboard_info, and pids + super().__init__() self.chdr_endpoint = ChdrEndpoint(self.log, self.config) @@ -105,8 +108,7 @@ class sim(PeriphManagerBase): self.log.debug("Setting Simulator Sample Rate to {}".format(freq)) self.chdr_endpoint.set_sample_rate(freq) - @classmethod - def generate_device_info(cls, eeprom_md, mboard_info, dboard_infos): + def generate_device_info(self, eeprom_md, mboard_info, dboard_infos): """ Hard-code our product map """ @@ -115,25 +117,23 @@ class sim(PeriphManagerBase): eeprom_md, mboard_info, dboard_infos) # Then add device-specific information mb_pid = eeprom_md.get('pid') - device_info['product'] = cls.pids.get(mb_pid, 'unknown') + device_info['product'] = self.pids.get(mb_pid, 'unknown') return device_info def _read_mboard_eeprom(self): """ Read out a simulated mboard eeprom and saves it to the appropriate member variable """ - self._eeprom_head = sim._generate_eeprom_head() + self._eeprom_head = self._generate_eeprom_head() self.log.trace("Found EEPROM metadata: '{}'" .format(str(self._eeprom_head))) return (self._eeprom_head, None) - @staticmethod - def _generate_eeprom_head(serial=b'3196D2A', rev=2, rev_compat=2): - return {'pid': 0xE320, - 'rev': rev, - 'rev_compat': rev_compat, - 'serial': serial} + def _generate_eeprom_head(self): + return {'pid': self.config.hardware.pid, + 'rev': 0, + 'serial': self.config.hardware.serial_num} def _init_peripherals(self, args): """ @@ -157,8 +157,11 @@ class sim(PeriphManagerBase): self.log.debug("Device info: {}".format(self.device_info)) def _init_dboards(self, dboard_infos, override_dboard_pids, default_args): - self.dboards.append(SimulatedCatalinaDboard( - E320_DBOARD_SLOT_IDX, self._simulator_sample_rate)) + # TODO: Support more than one Daughter Board + # Needs changes here and in config.py + dboard_name = self.config.hardware.dboard_class + dboard_class = dboards[dboard_name] + self.dboards.append(dboard_class(E320_DBOARD_SLOT_IDX, self._simulator_sample_rate)) self.log.info("Found %d daughterboard(s).", len(self.dboards)) ########################################################################### diff --git a/mpm/python/usrp_mpm/simulator/CMakeLists.txt b/mpm/python/usrp_mpm/simulator/CMakeLists.txt index 21f281e52..ef3c9bc8c 100644 --- a/mpm/python/usrp_mpm/simulator/CMakeLists.txt +++ b/mpm/python/usrp_mpm/simulator/CMakeLists.txt @@ -12,7 +12,7 @@ set(USRP_MPM_FILES ${USRP_MPM_FILES}) set(USRP_MPM_SIMULATOR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py ${CMAKE_CURRENT_SOURCE_DIR}/sim_dboard.py - ${CMAKE_CURRENT_SOURCE_DIR}/sim_dboard_catalina.py + ${CMAKE_CURRENT_SOURCE_DIR}/hardware_presets.py ${CMAKE_CURRENT_SOURCE_DIR}/chdr_endpoint.py ${CMAKE_CURRENT_SOURCE_DIR}/noc_block_regs.py ${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_graph.py diff --git a/mpm/python/usrp_mpm/simulator/chdr_endpoint.py b/mpm/python/usrp_mpm/simulator/chdr_endpoint.py index 17d707ae7..bca7f9c69 100644 --- a/mpm/python/usrp_mpm/simulator/chdr_endpoint.py +++ b/mpm/python/usrp_mpm/simulator/chdr_endpoint.py @@ -36,7 +36,8 @@ class ChdrEndpoint: self.send_queue = SelectableQueue() self.send_wrapper = SendWrapper(self.send_queue) - self.graph = RFNoCGraph(self.get_default_nodes(), self.log, 1, self.send_wrapper, CHDR_W) + self.graph = RFNoCGraph(self.get_default_nodes(), self.log, 0, self.send_wrapper, + CHDR_W, config.hardware.rfnoc_device_type) self.thread = Thread(target=self.socket_worker, daemon=True) self.thread.start() diff --git a/mpm/python/usrp_mpm/simulator/config.py b/mpm/python/usrp_mpm/simulator/config.py index a5f07950d..6698f9fd0 100644 --- a/mpm/python/usrp_mpm/simulator/config.py +++ b/mpm/python/usrp_mpm/simulator/config.py @@ -12,6 +12,47 @@ it identifies itself as. import configparser from .sample_source import sinks, sources, NullSamples +from .hardware_presets import presets +import numbers + +class HardwareDescriptor: + """This class contains the various magic numbers that are needed to + identify specific hardware to UHD + """ + def __init__(self, product, uhd_device_type, description, pid, serial_num, dboard_class, rfnoc_device_type): + """ + product -> MPM Product, stored in PeriphManager.mboard_info['product'] + e.g. "e320", "b200" + uhd_device_type -> A device_type string recognized by UHD's device discovery. + This is the same identifier that goes into --args=type="..." + e.g. "e3xx", "n3xx" + description -> MPM description, user visible. Stored in PeriphManager.description + pid -> motherboard pid, stored as a key in PeriphManager.pids + e.g. 0xE320 + serial_num -> Device Specific serial number. + e.g. "3196D2A" + dboard_class -> Python class which should be instantiated as a daughterboard + rfnoc_device_type -> Device Type read from NoC Core Registers + see defaults.hpp:device_type_t + e.g. 0xE320, 0xA300 + """ + self.product = product + self.uhd_device_type = uhd_device_type + self.description = description + self.pid = pid + self.serial_num = serial_num + self.dboard_class = dboard_class + self.rfnoc_device_type = rfnoc_device_type + + @classmethod + def from_dict(cls, dict): + return cls(dict['product'], + dict['uhd_device_type'], + dict['description'], + dict['pid'], + dict['serial_num'], + dict['dboard_class'], + dict['rfnoc_device_type']) class Config: """This class represents a configuration file for the usrp simulator. @@ -24,18 +65,41 @@ class Config: sample_source.py). The other key value pairs in the section are passed to the source/sink constructor as strings through **kwargs """ - def __init__(self, source_gen, sink_gen): + def __init__(self, source_gen, sink_gen, hardware): self.source_gen = source_gen self.sink_gen = sink_gen + self.hardware = hardware @classmethod - def from_path(cls, path): + def from_path(cls, log, path): """Parse a config .ini file from a path""" parser = configparser.ConfigParser() parser.read(path) - source_gen = Config._read_sample_section(parser['sample.source'], sources) - sink_gen = Config._read_sample_section(parser['sample.sink'], sinks) - return cls(source_gen, sink_gen) + source_gen = NullSamples + # Here we read data from a section and then pop it. + # For some reason, you can't iterate over a section (needed to make a dict), + # after its been popped. + if 'sample.source' in parser: + source_gen = Config._read_sample_section(parser['sample.source'], sources) + parser.pop('sample.source') + sink_gen = NullSamples + if 'sample.sink' in parser: + sink_gen = Config._read_sample_section(parser['sample.sink'], sinks) + parser.pop('sample.sink') + hardware_section = dict(parser['hardware']) + preset_name = hardware_section.get('preset', None) + hardware_preset = presets[preset_name].copy() if preset_name is not None else {} + hardware_preset.update(hardware_section) + hardware = HardwareDescriptor.from_dict(hardware_preset) + parser.pop('hardware') + for unused_section in parser: + # Python sticks this into all config files + if unused_section == 'DEFAULT': + continue + # This helps stop you from shooting yourself in the foot when you add + # the [sampel.sink] section + log.warning("Unrecognized section in config file: {}".format(unused_section)) + return cls(source_gen, sink_gen, hardware) @staticmethod def _read_sample_section(section, lookup): @@ -49,4 +113,8 @@ class Config: @classmethod def default(cls): """Return a default config""" - return cls(NullSamples, NullSamples) + hardware = dict(presets['E320']) + # For the uninitiated, this is how you spell Fake Device in hex + hardware['serial_num'] = "FA4EDE7" + hardware = HardwareDescriptor.from_dict(hardware) + return cls(NullSamples, NullSamples, hardware) diff --git a/mpm/python/usrp_mpm/simulator/hardware_presets.py b/mpm/python/usrp_mpm/simulator/hardware_presets.py new file mode 100644 index 000000000..b6747bb88 --- /dev/null +++ b/mpm/python/usrp_mpm/simulator/hardware_presets.py @@ -0,0 +1,10 @@ +presets = {} + +presets['E320'] = { + 'product': "e320", + 'uhd_device_type': "e3xx", + 'description': "E320-Series Device - SIMULATED", + 'pid': 0xE320, + 'dboard_class': "SimulatedCatalinaDboard", + 'rfnoc_device_type': 0xE320 +} diff --git a/mpm/python/usrp_mpm/simulator/rfnoc_graph.py b/mpm/python/usrp_mpm/simulator/rfnoc_graph.py index ada6e70b0..a2b536ed2 100644 --- a/mpm/python/usrp_mpm/simulator/rfnoc_graph.py +++ b/mpm/python/usrp_mpm/simulator/rfnoc_graph.py @@ -148,7 +148,7 @@ class RFNoCGraph: It serves as an interface between the ChdrEndpoint and the individual blocks/nodes. """ - def __init__(self, graph_list, log, device_id, send_wrapper, chdr_w): + def __init__(self, graph_list, log, device_id, send_wrapper, chdr_w, rfnoc_device_id): self.log = log.getChild("Graph") self.device_id = device_id self.stream_spec = StreamSpec() @@ -173,8 +173,8 @@ class RFNoCGraph: (NocBlockPort(0, 1), StreamEndpointPort(0, 1)) ] self.regs = NocBlockRegs(self.log, 1 << 16, True, 1, [radio], len(self.stream_ep), 1, - 0xE320, adj_list, 8, 1, self.get_stream_spec, self.radio_tx_cmd, - self.radio_tx_stop) + rfnoc_device_id, adj_list, 8, 1, self.get_stream_spec, + self.radio_tx_cmd, self.radio_tx_stop) def radio_tx_cmd(self, sep_block_id): """Triggers the creation of a ChdrOutputStream in the ChdrEndpoint using diff --git a/mpm/python/usrp_mpm/simulator/sim_dboard.py b/mpm/python/usrp_mpm/simulator/sim_dboard.py index a264eae9c..90f8be724 100644 --- a/mpm/python/usrp_mpm/simulator/sim_dboard.py +++ b/mpm/python/usrp_mpm/simulator/sim_dboard.py @@ -7,6 +7,11 @@ from usrp_mpm.dboard_manager import DboardManagerBase from usrp_mpm.mpmlog import get_logger from usrp_mpm.mpmutils import to_native_str +registry = {} + +def register_dboard_class(cls): + registry[cls.__name__] = cls + class SimulatedDboardBase(DboardManagerBase): """ A class to simulate daughterboards in a simulated device. @@ -57,3 +62,32 @@ class SimulatedDboardBase(DboardManagerBase): self.log.debug("Called {} with args: {}".format(prop_name, args)) return func(*args) setattr(self, prop_name, wrapped_func) + +class SimulatedCatalinaDboard(SimulatedDboardBase): + pids = [0x0110] + + extra_methods = [ + ("set_gain", lambda target, gain: gain), + ("catalina_tune", lambda which, freq: freq), + ("set_bw_filter", lambda which, freq: freq), + "set_dc_offset_auto", + "set_iq_balance_auto", + "set_agc", + "set_active_chains", + "set_timing_mode", + "data_port_loopback" + ] + + def __init__(self, slot_idx, clock_rate_cb, **kwargs): + super().__init__(slot_idx, **kwargs) + self.clock_rate_cb = clock_rate_cb + self.master_clock_rate = 122.88e6 + + def get_master_clock_rate(self): + return self.master_clock_rate + + def set_catalina_clock_rate(self, rate): + self.clock_rate_cb(rate) + return rate + +register_dboard_class(SimulatedCatalinaDboard) diff --git a/mpm/python/usrp_mpm/simulator/sim_dboard_catalina.py b/mpm/python/usrp_mpm/simulator/sim_dboard_catalina.py deleted file mode 100644 index 4e46d1410..000000000 --- a/mpm/python/usrp_mpm/simulator/sim_dboard_catalina.py +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2020 Ettus Research, a National Instruments Brand -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -from .sim_dboard import SimulatedDboardBase - -class SimulatedCatalinaDboard(SimulatedDboardBase): - pids = [0x0110] - - extra_methods = [ - ("set_gain", lambda target, gain: gain), - ("catalina_tune", lambda which, freq: freq), - ("set_bw_filter", lambda which, freq: freq), - "set_dc_offset_auto", - "set_iq_balance_auto", - "set_agc", - "set_active_chains", - "set_timing_mode", - "data_port_loopback" - ] - - def __init__(self, slot_idx, clock_rate_cb, **kwargs): - super().__init__(slot_idx, **kwargs) - self.clock_rate_cb = clock_rate_cb - self.master_clock_rate = 122.88e6 - - def get_master_clock_rate(self): - return self.master_clock_rate - - def set_catalina_clock_rate(self, rate): - self.clock_rate_cb(rate) - return rate |