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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
#
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""This module includes all of the components necessary to simulate the
configuration of a NoC Core and NoC Blocks.
This module handles and responds to Management and Ctrl Packets. It
also instantiates the registers and acts as an interface between
the chdr packets on the network and the registers.
"""
from uhd.chdr import MgmtOpCode, MgmtOpCfg, MgmtOpSelDest
from .noc_block_regs import NocBlockRegs, NocBlock, StreamEndpointPort, NocBlockPort
from .rfnoc_common import Node, NodeType, StreamSpec, to_iter, swap_src_dst, RETURN_TO_SENDER
from .stream_endpoint_node import StreamEndpointNode
class XportNode(Node):
"""Represents an Xport node
When an Advertise Management op is received, the address and
src_epid of the packet is placed in self.addr_map
"""
def __init__(self, node_inst):
super().__init__(node_inst)
self.downstream = None
self.addr_map = {}
def get_type(self):
return NodeType.XPORT
def _handle_mgmt_packet(self, packet, addr, **kwargs):
send_upstream = False
payload = packet.get_payload_mgmt()
our_hop = payload.pop_hop()
packet.set_payload(payload)
for op in to_iter(our_hop.get_op, our_hop.get_num_ops()):
if op.op_code == MgmtOpCode.INFO_REQ:
payload.get_hop(0).add_op(self.info_response(0))
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.ADVERTISE:
self.log.info("Advertise: {} | EPID:{} -> EPID:{}"
.format(self.get_id(), payload.src_epid,
packet.get_header().dst_epid))
self.log.info("addr_map updated: EPID:{} -> {}".format(payload.src_epid, addr))
self.addr_map[payload.src_epid] = addr
else:
raise NotImplementedError(op.op_code)
self.log.trace("Xport {} processed hop:\n{}"
.format(self.node_inst, our_hop))
packet.set_payload(payload)
if send_upstream:
return RETURN_TO_SENDER
return self.downstream
def _handle_default_packet(self, packet, **kwargs):
return self.downstream
class XbarNode(Node):
"""Represents a crossbar node
self.routing_table stores a mapping of dst_epids to xbar ports
port numbers start from 0. xports are addressed first
stream ep ports are then addressed where
the index of the first ep = len(xport_ports)
NOTE: UHD inteprets the node_inst of a xbar as the port which
it is connected to. This differs from its handling of node_inst
for other types of nodes.
"""
def __init__(self, node_inst, ports, xport_ports):
super().__init__(node_inst)
self.nports = len(ports) + len(xport_ports)
self.nports_xport = len(xport_ports)
self.ports = xport_ports + ports
self.routing_table = {}
def get_type(self):
return NodeType.XBAR
def _handle_mgmt_packet(self, packet, **kwargs):
send_upstream = False
destination = None
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:
payload.get_hop(0).add_op(self.info_response(
(self.nports_xport << 8) | self.nports))
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.ADVERTISE:
self.log.info("Advertise: {}".format(self.get_id()))
elif op.op_code == MgmtOpCode.CFG_WR_REQ:
cfg = op.get_op_payload()
cfg = MgmtOpCfg.parse(cfg)
self.routing_table[cfg.addr] = cfg.data
self.log.debug("Xbar {} routing changed: {}"
.format(self.node_inst, self.routing_table))
elif op.op_code == MgmtOpCode.SEL_DEST:
cfg = op.get_op_payload()
cfg = MgmtOpSelDest.parse(cfg)
dest_port = cfg.dest
destination = self.ports[dest_port]
else:
raise NotImplementedError(op.op_code)
self.log.trace("Xbar {} processed hop:\n{}"
.format(self.node_inst, our_hop))
packet.set_payload(payload)
if send_upstream:
return RETURN_TO_SENDER
elif destination is not None:
return destination
else:
return self._handle_default_packet(packet, **kwargs)
def _handle_default_packet(self, packet, **kwargs):
dst_epid = packet.get_header().dst_epid
if dst_epid not in self.routing_table:
raise RuntimeError("Xbar no destination for packet (dst_epid: {})".format(dst_epid))
return self.ports[self.routing_table[dst_epid]]
def from_index(self, nodes):
"""This iterates through the list of nodes and sets the
reference in self.ports to the node's id
"""
for i in range(len(self.ports)):
nodes_index = self.ports[i]
node = nodes[nodes_index]
self.ports[i] = node.get_local_id()
if node.__class__ is XportNode:
node.downstream = self.get_local_id()
elif node.__class__ is StreamEndpointNode:
node.upstream = self.get_local_id()
class RFNoCGraph:
"""This class holds all of the nodes of the NoC core and the Noc
blocks.
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, rfnoc_device_id):
self.log = log.getChild("Graph")
self.device_id = device_id
self.stream_spec = StreamSpec()
self.stream_ep = []
for node in graph_list:
if node.__class__ is StreamEndpointNode:
self.stream_ep.append(node)
node.graph_init(self.log, self.get_device_id, send_wrapper=send_wrapper,
chdr_w=chdr_w, dst_to_addr=self.dst_to_addr)
# These must be done sequentially so that get_device_id is initialized on all nodes
# before from_index is called on any node
for node in graph_list:
node.from_index(graph_list)
self.graph_map = {node.get_local_id(): node
for node in graph_list}
# For now, just use one radio block and hardcode it to the first stream endpoint
radio = NocBlock(1 << 16, 2, 2, 512, 1, 0x12AD1000, 16)
adj_list = [
(StreamEndpointPort(0, 0), NocBlockPort(0, 0)),
(StreamEndpointPort(0, 1), NocBlockPort(0, 1)),
(NocBlockPort(0, 0), StreamEndpointPort(0, 0)),
(NocBlockPort(0, 1), StreamEndpointPort(0, 1))
]
self.regs = NocBlockRegs(self.log, 1 << 16, True, 1, [radio], len(self.stream_ep), 1,
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
the current stream_spec.
This method transforms the sep_block_id into an epid useable by
the transmit code
"""
# TODO: Use the port
sep_blk, sep_port = sep_block_id
# The NoC Block index for stream endpoints is the inst + 1
# See rfnoc_graph.cpp:rfnoc_graph_impl#_init_sep_map()
sep_inst = sep_blk - 1
sep_id = (NodeType.STRM_EP, sep_inst)
stream_ep = self.graph_map[sep_id]
self.stream_spec.addr = self.dst_to_addr(stream_ep)
self.log.info("Streaming with StreamSpec:")
self.log.info(str(self.stream_spec))
stream_ep.begin_output(self.stream_spec)
def radio_tx_stop(self, sep_block_id):
"""Triggers the destuction of a ChdrOutputStream in the ChdrEndpoint
This method transforms the sep_block_id into an epid useable by
the transmit code
"""
sep_blk, sep_port = sep_block_id
sep_inst = sep_blk - 1
# The NoC Block index for stream endpoints is the inst + 1
# See rfnoc_graph.cpp:rfnoc_graph_impl#_init_sep_map()
sep_id = (NodeType.STRM_EP, sep_inst)
stream_ep = self.graph_map[sep_id]
stream_ep.end_output()
def get_device_id(self):
return self.device_id
def set_device_id(self, device_id):
"""Set this graph's rfnoc device id"""
self.device_id = device_id
def change_spp(self, spp):
"""Change the Stream Samples per Packet"""
self.stream_spec.packet_samples = spp
def find_ep_by_id(self, epid):
"""Find a Stream Endpoint which identifies with epid"""
for node in self.graph_map.values():
if node.__class__ is StreamEndpointNode:
if node.epid == epid:
return node
# Fixme: This doesn't support intra-device connections
# i.e. connecting nodes by connecting two internal stream endpoints
#
# That would require looking at the adjacency list if we end up at
# another stream endpoint instead of an xport
def dst_to_addr(self, src_ep):
"""This function traverses backwards through the node graph,
starting from src_ep and taking the path traveled by a packet
heading for src_ep.dst_epid. When it encounters an xport, it
returns the address associated with the dst_epid in the xport's
addr_map
"""
current_node = src_ep
dst_epid = src_ep.dst_epid
while current_node.__class__ != XportNode:
if current_node.__class__ == StreamEndpointNode:
current_node = self.graph_map[current_node.upstream]
else:
# current_node is a xbar
port = current_node.routing_table[dst_epid]
upstream_id = current_node.ports[port]
current_node = self.graph_map[upstream_id]
return current_node.addr_map[dst_epid]
def handle_packet(self, packet, xport_input, addr, sender, num_bytes):
"""Given a chdr_packet, the id of an xport node to serve as an
entry point, and a source address, send the packet through the
node graph.
"""
node_id = xport_input
response_packet = None
while node_id is not None:
assert len(node_id) == 2, "Node returned non-local node_id of len {}: {}" \
.format(len(node_id), node_id)
if node_id[0] == NodeType.RTS:
response_packet = packet
break
node = self.graph_map[node_id]
# If the node returns a value, it is the node id of the
# node the packet should be passed to next
# or RETURN_TO_SENDER
node_id = node.handle_packet(packet, regs=self.regs, addr=addr,
sender=sender, num_bytes=num_bytes)
return response_packet
def get_stream_spec(self):
""" Get the current output stream configuration """
return self.stream_spec
|