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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
#
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
This is a stream endpoint node, which is used in the RFNoCGraph class.
It also houses the logic to create output and input streams.
"""
from uhd.chdr import MgmtOpCode, MgmtOpCfg, MgmtOp, PacketType, CtrlStatus, CtrlOpCode, \
ChdrHeader, StrcOpCode, StrcPayload, ChdrPacket, StrsStatus
from .rfnoc_common import Node, NodeType, to_iter, swap_src_dst, RETURN_TO_SENDER
from .stream_ep_regs import StreamEpRegs, STRM_STATUS_FC_ENABLED
from .chdr_stream import ChdrOutputStream, ChdrInputStream
class StreamEndpointNode(Node):
"""Represents a Stream endpoint node
This class contains a StreamEpRegs object. To clarify, management
packets access these registers, while control packets access the
registers of the noc_blocks which are held in the RFNoCGraph and
passed into handle_packet as the regs parameter
"""
def __init__(self, node_inst, source_gen, sink_gen):
super().__init__(node_inst)
self.epid = node_inst
self.dst_epid = None
self.upstream = None
# ---- These 4 aren't configurable right now
self.has_data = True
self.has_ctrl = True
self.input_ports = 1
self.output_ports = 1
# ----
self.input_stream = None
self.output_stream = None
self.chdr_w = None
self.send_wrapper = None
self.dst_to_addr = None
self.source_gen = source_gen
self.sink_gen = sink_gen
self.downstream_capacity = None
self.strs_handlers = {}
self.ep_regs = StreamEpRegs(self.get_epid, self.set_epid, self.set_dst_epid,
self.ctrl_status_callback_out, self.ctrl_status_callback_in,
4, 4 * 8000)
def ctrl_status_callback_out(self, status):
"""Called by the ep_regs when on a write to the
REG_OSTRM_CTRL_STATUS register
"""
if status.cfg_start:
# This only creates a new ChdrOutputStream if the cfg_start flag is set
self.log.info("Starting Stream EPID:{} -> EPID:{}".format(self.epid, self.dst_epid))
addr = self.dst_to_addr(self)
self.send_strc(addr)
def handle_initial_strs(strs_packet):
payload = strs_packet.get_payload_strs()
assert payload.status == StrsStatus.OKAY, \
"Received STRS Packet Err: {}".format(payload.status)
self.downstream_capacity = (payload.capacity_pkts, payload.capacity_bytes)
self.strs_handlers[self.epid] = handle_initial_strs
return STRM_STATUS_FC_ENABLED
def ctrl_status_callback_in(self, status):
"""Called by the ep_regs on a write to the
REG_OSTRM_CTRL_STATUS register
"""
# This always triggers the graph to create a new ChdrInputStream
self.begin_input()
return STRM_STATUS_FC_ENABLED
def graph_init(self, log, set_device_id, send_wrapper, chdr_w, dst_to_addr, **kwargs):
super().graph_init(log, set_device_id)
self.ep_regs.log = log
self.chdr_w = chdr_w
self.send_wrapper = send_wrapper
self.dst_to_addr = dst_to_addr
def get_type(self):
return NodeType.STRM_EP
def get_epid(self):
"""Get this endpoint's endpoint id"""
return self.epid
def set_epid(self, epid):
"""Set this endpoint's endpoint id"""
self.epid = epid
def set_dst_epid(self, dst_epid):
"""Set this endpoint's destination endpoint id"""
self.dst_epid = dst_epid
def _handle_mgmt_packet(self, packet, **kwargs):
send_upstream = False
payload = packet.get_payload_mgmt()
our_hop = payload.pop_hop()
for op in to_iter(our_hop.get_op, our_hop.get_num_ops()):
if op.op_code == MgmtOpCode.INFO_REQ:
ext_info = 0
ext_info |= (1 if self.has_ctrl else 0) << 0
ext_info |= (1 if self.has_data else 0) << 1
ext_info |= (self.input_ports & 0x3F) << 2
ext_info |= (self.output_ports & 0x3F) << 8
payload.get_hop(0).add_op(self.info_response(ext_info))
elif op.op_code == MgmtOpCode.RETURN:
send_upstream = True
swap_src_dst(packet, payload)
elif op.op_code == MgmtOpCode.NOP:
pass
elif op.op_code == MgmtOpCode.CFG_RD_REQ:
request = MgmtOpCfg.parse(op.get_op_payload())
value = self.ep_regs.read(request.addr)
payload.get_hop(0).add_op(MgmtOp(MgmtOpCode.CFG_RD_RESP,
MgmtOpCfg(request.addr, value)))
elif op.op_code == MgmtOpCode.CFG_WR_REQ:
request = MgmtOpCfg.parse(op.get_op_payload())
self.ep_regs.write(request.addr, request.data)
else:
raise NotImplementedError("op_code {} is not implemented for StreamEndpointNode"
.format(op.op_code))
self.log.trace("Stream Endpoint {} processed hop:\n{}"
.format(self.node_inst, our_hop))
packet.set_payload(payload)
if send_upstream:
return RETURN_TO_SENDER
self.log.trace("Stream Endpoint {} received packet:\n{}"
.format(self.node_inst, packet))
def _handle_ctrl_packet(self, packet, regs, **kwargs):
payload = packet.get_payload_ctrl()
swap_src_dst(packet, payload)
if payload.status != CtrlStatus.OKAY:
raise RuntimeError("Control Status not OK: {}".format(payload.status))
if payload.op_code == CtrlOpCode.READ:
payload.is_ack = True
payload.set_data([regs.read(payload.address)])
elif payload.op_code == CtrlOpCode.WRITE:
payload.is_ack = True
regs.write(payload.address, payload.get_data()[0])
else:
raise NotImplementedError("Unknown Control OpCode: {}".format(payload.op_code))
packet.set_payload(payload)
return RETURN_TO_SENDER
def _handle_data_packet(self, packet, num_bytes, sender, **kwargs):
assert self.input_stream is not None
self.input_stream.queue_packet(packet, num_bytes, sender)
def _handle_strs_packet(self, packet, **kwargs):
header = packet.get_header()
if header.dst_epid in self.strs_handlers:
self.strs_handlers.pop(header.dst_epid)(packet)
else:
self.output_stream.queue_packet(packet)
def _handle_strc_packet(self, packet, **kwargs):
self._handle_data_packet(packet, **kwargs)
def send_strc(self, addr):
"""Send a Stream Command packet from the specified stream_ep
to the specified address.
This is not handled in ChdrOutputStream because the STRC must be
dispatched before UHD configures the samples per packet,
which is required to complete a StreamSpec
"""
header = ChdrHeader()
header.dst_epid = self.dst_epid
header.pkt_type = PacketType.STRC
payload = StrcPayload()
payload.src_epid = self.epid
payload.op_code = StrcOpCode.INIT
payload.num_pkts = 10
payload.num_bytes = 10 * 1024 # 10 KB
packet = ChdrPacket(self.chdr_w, header, payload)
self.send_wrapper.send_packet(packet, addr)
def begin_output(self, stream_spec):
"""Spin up a new ChdrOutputStream thread which transmits from src_epid
according to stream_spec.
This is triggered from RFNoC Graph when the radio receives a
Stream Command
"""
# As of now, only one stream endpoint port per stream endpoint
# is supported.
assert self.output_stream is None, \
"Output Stream already running on epid: {}".format(self.epid)
stream_spec.dst_epid = self.dst_epid
stream_spec.capacity_packets = self.downstream_capacity[0]
stream_spec.capacity_bytes = self.downstream_capacity[1]
self.downstream_capacity = None
self.output_stream = ChdrOutputStream(self.log, self.chdr_w, self.source_gen(),
stream_spec, self.send_wrapper)
def end_output(self):
"""Stops src_epid's current transmission. This opens up the sep
to new transmissions in the future.
This is triggered either by the RFNoC Graph when the radio
receives a Stop Stream command or when the transmission has no
more samples to send.
"""
self.output_stream.finish()
self.output_stream = None
def begin_input(self):
"""Spin up a new ChdrInputStream thread which receives all data and strc
This is triggered by RFNoC Graph when there is a write to the
REG_ISTRM_CTRL_STATUS register
"""
# As of now, only one stream endpoint port per stream endpoint
# is supported.
# Rx streams aren't explicitly ended by UHD. If we try to start
# a new one on the same epid, just quietly close the old one.
if self.input_stream is not None:
self.input_stream.finish()
self.input_stream = ChdrInputStream(self.log, self.chdr_w,
self.sink_gen(), self.send_wrapper, self.epid)
|