diff options
Diffstat (limited to 'mpm/python/usrp_mpm/simulator/rfnoc_common.py')
-rw-r--r-- | mpm/python/usrp_mpm/simulator/rfnoc_common.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/simulator/rfnoc_common.py b/mpm/python/usrp_mpm/simulator/rfnoc_common.py new file mode 100644 index 000000000..d7f61a4c1 --- /dev/null +++ b/mpm/python/usrp_mpm/simulator/rfnoc_common.py @@ -0,0 +1,202 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +This file contains common classes that are used by both rfnoc_graph.py +and stream_endpoint_node.py +""" +from enum import IntEnum +from uhd.chdr import MgmtOpCode, MgmtOpNodeInfo, MgmtOp, PacketType + +def to_iter(index_func, length): + """Allows looping over an indexed object in a for-each loop""" + for i in range(length): + yield index_func(i) + +def swap_src_dst(packet, payload): + """Swap the src_epid and the dst_epid of a packet""" + header = packet.get_header() + our_epid = header.dst_epid + header.dst_epid = payload.src_epid + payload.src_epid = our_epid + packet.set_header(header) + +class NodeType(IntEnum): + """The type of a node in a NoC Core + + RTS is a magic value used to determine when to return a packet to + the sender + """ + INVALID = 0 + XBAR = 1 + STRM_EP = 2 + XPORT = 3 + RTS = 0xFF + +RETURN_TO_SENDER = (NodeType.RTS, 0) + +class Node: + """Represents a node in a NoC Core + + These objects are usually constructed by the caller of RFNoC Graph. + Initially, they have references to other nodes as indexes of the + list of nodes. The graph calls graph_init and from_index so this + node can initialize their references to node_ids instead. + """ + def __init__(self, node_inst): + """node_inst should start at 0 and increase for every Node of + the same type that is constructed + """ + self.node_inst = node_inst + self.log = None + self.get_device_id = None + + def graph_init(self, log, get_device_id, **kwargs): + """This method is called to initialize the Node Graph""" + self.log = log + self.get_device_id = get_device_id + + def get_id(self): + """Return the NodeID (device_id, node_type, node_inst)""" + return (self.get_device_id(), self.get_type(), self.node_inst) + + def get_local_id(self): + """Return the Local NodeID (node_type, node_inst)""" + return (self.get_type(), self.node_inst) + + def get_type(self): + """Returns the NodeType of this node""" + raise NotImplementedError + + def handle_packet(self, packet, **kwargs): + """Processes a packet + The return value should be a node_id or RETURN_TO_SENDER + """ + packet_type = packet.get_header().pkt_type + next_node = None + if packet_type == PacketType.MGMT: + next_node = self._handle_mgmt_packet(packet, **kwargs) + elif packet_type == PacketType.CTRL: + next_node = self._handle_ctrl_packet(packet, **kwargs) + elif packet_type in (PacketType.DATA_WITH_TS, PacketType.DATA_NO_TS): + next_node = self._handle_data_packet(packet, **kwargs) + elif packet_type == PacketType.STRS: + next_node = self._handle_strs_packet(packet, **kwargs) + elif packet_type == PacketType.STRC: + next_node = self._handle_strc_packet(packet, **kwargs) + else: + raise RuntimeError("Invalid Enum Value for PacketType: {}".format(packet_type)) + # If the specific method for that packet isn't implemented, try the general method + if next_node == NotImplemented: + next_node = self._handle_default_packet(packet, **kwargs) + return next_node + + # pylint: disable=unused-argument,no-self-use + def _handle_mgmt_packet(self, packet, **kwargs): + return NotImplemented + + # pylint: disable=unused-argument,no-self-use + def _handle_ctrl_packet(self, packet, **kwargs): + return NotImplemented + + # pylint: disable=unused-argument,no-self-use + def _handle_data_packet(self, packet, **kwargs): + return NotImplemented + + # pylint: disable=unused-argument,no-self-use + def _handle_strc_packet(self, packet, **kwargs): + return NotImplemented + + # pylint: disable=unused-argument,no-self-use + def _handle_strs_packet(self, packet, **kwargs): + return NotImplemented + + def _handle_default_packet(self, packet, **kwargs): + raise RuntimeError("{} has no operation defined for a {} packet" + .format(self.__class__, packet.get_header().pkt_type)) + + def from_index(self, nodes): + """Initialize this node's indexes to block_id references""" + pass + + def info_response(self, extended_info): + """Generate a node info response MgmtOp""" + return MgmtOp( + op_payload=MgmtOpNodeInfo(self.get_device_id(), self.get_type(), + self.node_inst, extended_info), + op_code=MgmtOpCode.INFO_RESP + ) + +class StreamSpec: + """This class carries the configuration parameters of a Tx stream. + + total_samples, is_continuous, and packet_samples come from the + radio registers (noc_block_regs.py) + + sample_rate comes from an rpc to the daughterboard (through the + set_sample_rate method in chdr_endpoint.py) + + dst_epid comes from the source stream_ep + + addr comes from the xport passed through when routing to dst_epid + """ + LOW_MASK = 0xFFFFFFFF + HIGH_MASK = (0xFFFFFFFF) << 32 + def __init__(self): + self.init_timestamp = 0 + self.is_timed = False + self.total_samples = 0 + self.is_continuous = True + self.packet_samples = None + self.sample_rate = None + self.dst_epid = None + self.addr = None + + def set_timestamp_lo(self, low): + """Set the low 32 bits of the initial timestamp""" + self.init_timestamp = (self.init_timestamp & (StreamSpec.HIGH_MASK)) \ + | (low & StreamSpec.LOW_MASK) + + def set_timestamp_hi(self, high): + """Set the high 32 bits of the initial timestamp""" + self.init_timestamp = (self.init_timestamp & StreamSpec.LOW_MASK) \ + | ((high & StreamSpec.LOW_MASK) << 32) + + def set_num_words_lo(self, low): + """Set the low 32 bits of the total_samples field""" + self.total_samples = (self.total_samples & (StreamSpec.HIGH_MASK)) \ + | (low & StreamSpec.LOW_MASK) + + def set_num_words_hi(self, high): + """Set the high 32 bits of the total_samples field""" + self.total_samples = (self.total_samples & StreamSpec.LOW_MASK) \ + | ((high & StreamSpec.LOW_MASK) << 32) + + def seconds_per_packet(self): + """Calculates how many seconds should be between each packet + transmit + """ + assert self.packet_samples != 0 + assert self.sample_rate != 0 + return self.packet_samples / self.sample_rate + + def seq_num_iter(self): + """Returns a generator which returns an incrementing integer + for each packet that should be sent. This is useful to set the + seq_num of each transmitted packet. + """ + i = 0 + while True: + if not self.is_continuous: + if i >= self.total_samples: + return + yield i + i += 1 + + def __str__(self): + return "StreamSpec{{total_samples: {}, is_continuous: {}, packet_samples: {}," \ + "sample_rate: {}, dst_epid: {}, addr: {}}}" \ + .format(self.total_samples, self.is_continuous, self.packet_samples, + self.sample_rate, self.dst_epid, self.addr) |