diff options
author | Samuel O'Brien <sam.obrien@ni.com> | 2020-07-21 14:20:24 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-10-08 07:44:12 -0500 |
commit | ee9085a494d6f5030e49f5a47aff6a84008e0852 (patch) | |
tree | 1234c4a6aa5c88f3df290c1586e69e4a91d24f7d /mpm/python/usrp_mpm/simulator/noc_block_regs.py | |
parent | f54a22c60a0cbe990c9d3892f4c565d64226196b (diff) | |
download | uhd-ee9085a494d6f5030e49f5a47aff6a84008e0852.tar.gz uhd-ee9085a494d6f5030e49f5a47aff6a84008e0852.tar.bz2 uhd-ee9085a494d6f5030e49f5a47aff6a84008e0852.zip |
sim: Simulator CHDR Parsing and RFNoC Graph
This commit adds a simulated RFNoC Graph to the simulator. It is also
able to process management and control packets which can traverse the
graph and read from simulated registers. Stub callbacks for creating
streams have been provided but are not implemented yet.
Signed-off-by: Samuel O'Brien <sam.obrien@ni.com>
Diffstat (limited to 'mpm/python/usrp_mpm/simulator/noc_block_regs.py')
-rw-r--r-- | mpm/python/usrp_mpm/simulator/noc_block_regs.py | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/simulator/noc_block_regs.py b/mpm/python/usrp_mpm/simulator/noc_block_regs.py new file mode 100644 index 000000000..b488ed43f --- /dev/null +++ b/mpm/python/usrp_mpm/simulator/noc_block_regs.py @@ -0,0 +1,312 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +# Read Register Addresses +#! Register address of the protocol version +PROTOVER_ADDR = 0 * 4 +#! Register address of the port information +PORT_CNT_ADDR = 1 * 4 +#! Register address of the edge information +EDGE_CNT_ADDR = 2 * 4 +#! Register address of the device information +DEVICE_INFO_ADDR = 3 * 4 +#! Register address of the controlport information +CTRLPORT_CNT_ADDR = 4 * 4 +#! (Write) Register address of the flush and reset controls +FLUSH_RESET_ADDR = 1 * 4 + +#! Base address of the adjacency list +ADJACENCY_BASE_ADDR = 0x10000 +#! Each port is allocated this many registers in the backend register space +REGS_PER_PORT = 16 + +REG_RX_MAX_WORDS_PER_PKT = 0x28 +REG_RX_CMD_NUM_WORDS_HI = 0x1C +REG_RX_CMD_NUM_WORDS_LO = 0x18 +REG_RX_CMD = 0x14 + +RX_CMD_CONTINUOUS = 0x2 +RX_CMD_STOP = 0x0 +RX_CMD_FINITE = 0x1 + +RADIO_BASE_ADDR = 0x1000 +REG_CHAN_OFFSET = 128 # 0x80 + + +class StreamEndpointPort: + """Represents a port on a Stream Endpoint + + inst should be the same as the stream endpoint's node_inst + """ + def __init__(self, inst, port): + self.inst = inst + self.port = port + def to_tuple(self, num_stream_ep): + # The entry in an adjacency list is (blk_id, block_port) + # where blk_id for stream endpoints starts at 1 + # and Noc Blocks are addressed after the last stream endpoint + # See rfnoc_graph.cpp + return (1 + self.inst, self.port) + +class NocBlockPort: + """Represents a port on a Noc Block""" + def __init__(self, inst, port): + self.inst = inst + self.port = port + + def to_tuple(self, num_stream_ep): + # The entry in an adjacency list is (blk_id, block_port) + # where blk_id for stream endpoints starts at 1 + # and Noc Blocks are addressed after the last stream endpoint + # See rfnoc_graph.cpp + return (1 + num_stream_ep + self.inst, self.port) + +class NocBlock: + """Represents a NocBlock + + see client_zero.hpp:block_config_info + + NOTE: The mtu in bytes is calculated by (2**data_mtu * CHDR_W) + """ + def __init__(self, protover, num_inputs, num_outputs, ctrl_fifo_size, + ctrl_max_async_msgs, noc_id, data_mtu): + self.protover = protover + self.num_inputs = num_inputs + self.num_outputs = num_outputs + self.ctrl_fifo_size = ctrl_fifo_size + self.ctrl_max_async_msgs = ctrl_max_async_msgs + self.noc_id = noc_id + self.data_mtu = data_mtu + + def read_reg(self, reg_num): + # See client_zero.cpp + if reg_num == 0: + return self.read_config() + elif reg_num == 1: + return self.read_noc_id() + elif reg_num == 2: + return self.read_data() + else: + raise RuntimeError("NocBlock doesn't have a register #{}".format(reg_num)) + + def read_config(self): + return (self.protover & 0x3F) | \ + ((self.num_inputs & 0x3F) << 6) | \ + ((self.num_outputs & 0x3F) << 12) | \ + ((self.ctrl_fifo_size & 0x3F) << 18) | \ + ((self.ctrl_max_async_msgs & 0xFF) << 24) + + def read_data(self): + return (self.data_mtu & 0x3F) << 2 | \ + (1 << 1) # Permanently Set flush done + + def read_noc_id(self): + return self.noc_id & 0xFFFFFFFF + +class NocBlockRegs: + """Represents registers associated whith a group of NoCBlocks + roughly similar to UHD's client_zero + + NOTE: Many write operations are currently unimplemented and simply no-op + """ + def __init__(self, log, protover, has_xbar, num_xports, blocks, num_stream_ep, num_ctrl_ep, + device_type, adjacency_list, sample_width, samples_per_cycle, get_stream_spec, + create_tx_stream, stop_tx_stream): + """ Args: + protover -> FPGA Compat number + has_xbar -> Is there a chdr xbar? + num_xports -> how many xports + blocks -> list of NocBlock objects + num_stream_ep -> how many stream endpoints + num_ctrl_ep -> how many ctrl endpoints + device_type -> the device type (see defaults.hpp:device_type_t in UHD) + adjacency_list -> List of (Port, Port) tuples where + Port is either StreamEndpointPort or NocBlockPort + sample_width -> Sample width of radio + samples_per_cycle -> Samples produced by a radio cycle + get_stream_spec -> Callback which returns the current stream spec + create_tx_stream -> Callback which takes a block_index and starts a tx stream + stop_tx_stream -> Callback which takes a block_index and stops a tx stream + """ + self.log = log.getChild("Regs") + self.protover = protover + self.has_xbar = has_xbar + self.num_xports = num_xports + self.blocks = blocks + self.num_blocks = len(blocks) + self.num_stream_ep = num_stream_ep + self.num_ctrl_ep = num_ctrl_ep + self.device_type = device_type + self.adjacency_list = [(src_blk.to_tuple(num_stream_ep), dst_blk.to_tuple(num_stream_ep)) + for src_blk, dst_blk in adjacency_list] + self.adjacency_list_reg = NocBlockRegs._parse_adjacency_list(self.adjacency_list) + self.sample_width = sample_width + self.samples_per_cycle = samples_per_cycle + self.radio_reg = {} + self.get_stream_spec = get_stream_spec + self.create_tx_stream = create_tx_stream + self.stop_tx_stream = stop_tx_stream + + def read(self, addr): + # See client_zero.cpp + if addr == PROTOVER_ADDR: + return self.read_protover() + elif addr == PORT_CNT_ADDR: + return self.read_port_cnt() + elif addr == EDGE_CNT_ADDR: + return self.read_edge_cnt() + elif addr == DEVICE_INFO_ADDR: + return self.read_device_info() + elif addr == CTRLPORT_CNT_ADDR: + return self.read_ctrlport_cnt() + elif addr >= 0x40 and addr < 0x1000: + return self.read_port_reg(addr) + # See radio_control_impl.cpp + elif addr >= 0x1000 and addr < 0x10000: + return self.read_radio(addr) + # See client_zero.cpp + elif addr >= 0x10000: + return self.read_adjacency_list(addr) + else: + raise RuntimeError("Unsupported register addr: 0x{:08X}".format(addr)) + + def read_radio(self, addr): + if addr == 0x1000: + raise NotImplementedError() # TODO: This should be REG_COMPAT + elif addr == 0x1004: + return self.read_radio_width() + else: + offset = addr - 0x1000 + chan = offset // 0x80 + radio_offset = offset % 0x80 + if radio_offset == 0x40: + return self.radio_reg + elif radio_offset == 0x3C: + return self.radio_reg + else: + raise NotImplementedError("Radio addr 0x{:08X} not implemented".format(addr)) + + def write_radio(self, addr, value): + """Write a value to radio registers + + See radio_control_impl.cpp + """ + offset = addr - 0x1000 + assert offset >= 0 + chan = offset // 0x80 + if chan > 0: # For now, just operate as if there is one channel + self.log.warn("Channel {} not suported".format(chan)) + return + reg = offset % 0x80 + if reg == REG_RX_MAX_WORDS_PER_PKT: + self.get_stream_spec().packet_samples = value + elif reg == REG_RX_CMD_NUM_WORDS_HI: + self.get_stream_spec().set_num_words_hi(value) + elif reg == REG_RX_CMD_NUM_WORDS_LO: + self.get_stream_spec().set_num_words_lo(value) + elif reg == REG_RX_CMD: + if value & (1 << 31) != 0: + self.log.warn("Timed Streams are not supported. Starting immediately") + value = value & ~(1 << 31) # Clear the flag + if value == RX_CMD_STOP: + sep_block_id = self.resolve_ep_towards_outputs((self.get_radio_port(), chan)) + self.stop_tx_stream(sep_block_id) + return + elif value == RX_CMD_CONTINUOUS: + self.get_stream_spec().is_continuous = True + elif value == RX_CMD_FINITE: + self.get_stream_spec().is_continuous = False + else: + raise RuntimeError("Unknown Stream RX_CMD: {:08X}".format(value)) + sep_block_id = self.resolve_ep_towards_outputs((self.get_radio_port(), chan)) + self.create_tx_stream(sep_block_id) + + def resolve_ep_towards_outputs(self, block_id): + """Follow dataflow downstream through the adjacency list until + a stream_endpoint is encountered + """ + for src_blk, dst_blk in self.adjacency_list: + if src_blk == block_id: + dst_index, dst_port = dst_blk + if dst_index <= self.num_stream_ep: + return dst_blk + else: + return self.resolve_ep_towards_outputs(dst_blk) + + def get_radio_port(self): + """Returns the block_id of the radio block""" + radio_noc_id = 0x12AD1000 + for i, block in enumerate(self.blocks): + if block.noc_id == radio_noc_id: + return i + 1 + self.num_stream_ep + + # This is the FPGA compat number + def read_protover(self): + return 0xFFFF & self.protover + + def read_port_cnt(self): + return (self.num_stream_ep & 0x3FF) | \ + ((self.num_blocks & 0x3FF) << 10) | \ + ((self.num_xports & 0x3FF) << 20) | \ + ((1 if self.has_xbar else 0) << 31) + + def read_edge_cnt(self): + return len(self.adjacency_list) + + def read_device_info(self): + return (self.device_type & 0xFFFF) << 16 + + def read_ctrlport_cnt(self): + return (self.num_ctrl_ep & 0x3FF) + + def read_adjacency_list(self, addr): + offset = addr & 0xFFFF + if offset == 0: + self.log.debug("Adjacency List has {} entries".format(len(self.adjacency_list_reg))) + return len(self.adjacency_list_reg) + else: + assert(offset % 4 == 0) + index = (offset // 4) - 1 + return self.adjacency_list_reg[index] + + def write(self, addr, value): + if addr == 0x1040 or addr == 0x10C0: + self.log.trace("Storing value: 0x:{:08X} to self.radio_reg for data loopback test".format(value)) + self.radio_reg = value + # assuming 2 channels, out of bounds is + # BASE + 2 * CHAN_OFFSET = 0x1000 + 2 * 0x80 = 0x1100 + elif 0x1000 <= addr < 0x1100: + self.write_radio(addr, value) + + def read_port_reg(self, addr): + port = addr // 0x40 + if port < self.num_stream_ep: + raise NotImplementedError() + else: + block = port - self.num_stream_ep - 1 + offset = (addr % 0x40) // 4 + return self.blocks[block].read_reg(offset) + + def read_radio_width(self): + return (self.samples_per_cycle & 0xFFFF) | \ + ((self.sample_width & 0xFFFF) << 16) + + @staticmethod + def _parse_adjacency_list(adj_list): + """Serialize an adjacency list from the form of + [((src_blk, src_port), (dst_blk, dst_port))] + + See client_zero.cpp:client_zero#_get_adjacency_list() + """ + def pack(blocks): + src_blk, src_port = blocks[0] + dst_blk, dst_port = blocks[1] + return ((src_blk & 0x3FF) << 22) | \ + ((src_port & 0x3F) << 16) | \ + ((dst_blk & 0x3FF) << 6) | \ + ((dst_port & 0x3F) << 0) + return [pack(blocks) for blocks in adj_list] + |