From 5df8202c0cb88fb5c7629fbf0ce5bed32c96e70d Mon Sep 17 00:00:00 2001 From: Samuel O'Brien Date: Thu, 30 Jul 2020 13:53:44 -0500 Subject: sim: Move Hardware Specific to Config File This commit moves various magic numbers and hardware specific settings into the configuration file. It also provides default presets for said configuration files which can be inherited from. Signed-off-by: Samuel O'Brien --- mpm/python/usrp_mpm/periph_manager/sim.py | 47 +++++++------ mpm/python/usrp_mpm/simulator/CMakeLists.txt | 2 +- mpm/python/usrp_mpm/simulator/chdr_endpoint.py | 3 +- mpm/python/usrp_mpm/simulator/config.py | 80 ++++++++++++++++++++-- mpm/python/usrp_mpm/simulator/hardware_presets.py | 10 +++ mpm/python/usrp_mpm/simulator/rfnoc_graph.py | 6 +- mpm/python/usrp_mpm/simulator/sim_dboard.py | 34 +++++++++ .../usrp_mpm/simulator/sim_dboard_catalina.py | 33 --------- 8 files changed, 149 insertions(+), 66 deletions(-) create mode 100644 mpm/python/usrp_mpm/simulator/hardware_presets.py delete mode 100644 mpm/python/usrp_mpm/simulator/sim_dboard_catalina.py (limited to 'mpm/python/usrp_mpm') 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 -- cgit v1.2.3