aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/simulator/rfnoc_common.py
blob: b1dab559f0fc94945d3e7c570f9c688315175419 (plain)
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)