1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
#
# 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
self.capacity_packets = 0
self.capacity_bytes = 0
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 __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)
|