uhd/host/lib/utils/utils_python.hpp
Samuel O'Brien 919a147afc python: Add bindings for C++ CHDR Parser
This commit adds pybind11 glue code for the userland chdr parsing code
introduced in the uhd::utils::chdr namespace. Additionally, it moves
some pybind11 adapter code to a common pybind_adaptors.hpp file which
originally existed in the cal_python.hpp file.

This commit also adds unit tests for the python bindings using a
captured wireshark trace which is located in rfnoc_packets_*.py and some
handwritten packets in hardcoded_packets.py

Signed-off-by: Samuel O'Brien <sam.obrien@ni.com>
2020-07-16 09:59:25 -05:00

369 lines
16 KiB
C++

//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/utils/chdr/chdr_packet.hpp>
#include <uhd/utils/pybind_adaptors.hpp>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
using namespace uhd::utils::chdr;
namespace chdr_rfnoc = uhd::rfnoc::chdr;
template <typename payload_t>
void export_utils_with_payload(
pybind11::class_<chdr_packet>& packet_class, std::string packet_name)
{
std::string get_payload_name("get_payload_");
get_payload_name.append(packet_name);
std::string to_string_with_payload_name("to_string_with_payload_");
to_string_with_payload_name.append(packet_name);
packet_class
.def(py::init([](uhd::rfnoc::chdr_w_t chdr_w,
chdr_rfnoc::chdr_header header,
payload_t payload,
boost::optional<uint64_t> timestamp,
std::vector<uint64_t> metadata) {
return chdr_packet(chdr_w, header, payload, timestamp, metadata);
}),
py::arg("chdr_w"),
py::arg("header"),
py::arg("payload"),
py::arg("timestamp") = boost::optional<uint64_t>(),
py::arg("metadata") = std::vector<uint64_t>())
.def(get_payload_name.c_str(),
&chdr_packet::get_payload<payload_t>,
py::arg("endianness") = uhd::ENDIANNESS_LITTLE)
.def("set_payload",
&chdr_packet::set_payload<payload_t>,
py::arg("payload"),
py::arg("endianness") = uhd::ENDIANNESS_LITTLE)
.def(to_string_with_payload_name.c_str(),
&chdr_packet::to_string_with_payload<payload_t>,
py::arg("endianness") = uhd::ENDIANNESS_LITTLE);
}
void export_utils(py::module& m)
{
py::enum_<uhd::endianness_t>(m, "Endianness")
.value("LITTLE", uhd::ENDIANNESS_LITTLE)
.value("BIG", uhd::ENDIANNESS_BIG);
py::enum_<uhd::rfnoc::chdr_w_t>(m, "ChdrWidth")
.value("W64", uhd::rfnoc::CHDR_W_64)
.value("W128", uhd::rfnoc::CHDR_W_128)
.value("W256", uhd::rfnoc::CHDR_W_256)
.value("W512", uhd::rfnoc::CHDR_W_512);
pybind11::class_<chdr_packet> packet_class =
py::class_<chdr_packet>(m, "ChdrPacket")
.def(py::init([](uhd::rfnoc::chdr_w_t chdr_w,
chdr_rfnoc::chdr_header header,
py::bytes& payload,
boost::optional<uint64_t> timestamp,
py::bytes& metadata) {
return chdr_packet(chdr_w,
header,
pybytes_to_vector(payload),
timestamp,
pybytes_to_u64_vector(metadata));
}),
py::arg("chdr_w"),
py::arg("header"),
py::arg("payload"),
py::arg("timestamp") = boost::optional<uint64_t>(),
py::arg("metadata") = py::bytes())
// Methods
.def("__str__", &chdr_packet::to_string)
.def("__repr__", &chdr_packet::to_string)
.def("get_header", &chdr_packet::get_header)
.def("set_header", &chdr_packet::set_header)
.def("get_timestamp", &chdr_packet::get_timestamp)
.def("set_timestamp", &chdr_packet::set_timestamp)
.def("get_metadata", &chdr_packet::get_metadata)
.def("serialize",
&chdr_packet::serialize_to_byte_vector,
py::arg("endianness") = uhd::ENDIANNESS_LITTLE)
.def_static(
"deserialize",
[](uhd::rfnoc::chdr_w_t chdr_w,
std::vector<uint8_t> bytes,
uhd::endianness_t endianness) {
return chdr_packet::deserialize(
chdr_w, bytes.begin(), bytes.end(), endianness);
},
py::arg("chdr_w"),
py::arg("bytes"),
py::arg("endianness") = uhd::ENDIANNESS_LITTLE)
.def_static(
"deserialize",
[](uhd::rfnoc::chdr_w_t chdr_w,
py::bytes bytes,
uhd::endianness_t endianness) {
std::vector<uint8_t> vector = pybytes_to_vector(bytes);
return chdr_packet::deserialize(
chdr_w, vector.begin(), vector.end(), endianness);
},
py::arg("chdr_w"),
py::arg("bytes"),
py::arg("endianness") = uhd::ENDIANNESS_LITTLE)
.def("get_packet_len", &chdr_packet::get_packet_len)
.def("get_payload_bytes", &chdr_packet::get_payload_bytes)
.def("set_payload_bytes", &chdr_packet::set_payload_bytes)
.def("set_payload_bytes", [](chdr_packet& self, py::bytes bytes) {
auto bytes_vector = pybytes_to_vector(bytes);
self.set_payload_bytes(bytes_vector);
});
export_utils_with_payload<chdr_rfnoc::ctrl_payload>(packet_class, "ctrl");
export_utils_with_payload<chdr_rfnoc::mgmt_payload>(packet_class, "mgmt");
export_utils_with_payload<chdr_rfnoc::strs_payload>(packet_class, "strs");
export_utils_with_payload<chdr_rfnoc::strc_payload>(packet_class, "strc");
py::class_<chdr_rfnoc::chdr_header>(m, "ChdrHeader")
// Constructor
.def(py::init<>())
// Methods
.def("__str__", &chdr_rfnoc::chdr_header::to_string)
.def("__repr__", &chdr_rfnoc::chdr_header::to_string)
// Properties
.def_property(
"vc", &chdr_rfnoc::chdr_header::get_vc, &chdr_rfnoc::chdr_header::set_vc)
.def_property(
"eob", &chdr_rfnoc::chdr_header::get_eob, &chdr_rfnoc::chdr_header::set_eob)
.def_property(
"eov", &chdr_rfnoc::chdr_header::get_eov, &chdr_rfnoc::chdr_header::set_eov)
.def_property("pkt_type",
&chdr_rfnoc::chdr_header::get_pkt_type,
&chdr_rfnoc::chdr_header::set_pkt_type)
.def_property("seq_num",
&chdr_rfnoc::chdr_header::get_seq_num,
&chdr_rfnoc::chdr_header::set_seq_num)
.def_property("length",
&chdr_rfnoc::chdr_header::get_length,
&chdr_rfnoc::chdr_header::set_length)
.def_property("dst_epid",
&chdr_rfnoc::chdr_header::get_dst_epid,
&chdr_rfnoc::chdr_header::set_dst_epid)
.def_property("num_mdata",
&chdr_rfnoc::chdr_header::get_num_mdata,
&chdr_rfnoc::chdr_header::set_num_mdata)
.def("pack", &chdr_rfnoc::chdr_header::pack);
py::enum_<chdr_rfnoc::packet_type_t>(m, "PacketType")
.value("MGMT", chdr_rfnoc::PKT_TYPE_MGMT)
.value("STRS", chdr_rfnoc::PKT_TYPE_STRS)
.value("STRC", chdr_rfnoc::PKT_TYPE_STRC)
.value("CTRL", chdr_rfnoc::PKT_TYPE_CTRL)
.value("DATA_NO_TS", chdr_rfnoc::PKT_TYPE_DATA_NO_TS)
.value("DATA_WITH_TS", chdr_rfnoc::PKT_TYPE_DATA_WITH_TS);
py::class_<chdr_rfnoc::ctrl_payload>(m, "CtrlPayload")
// Constructor
.def(py::init<>())
// Methods
.def("has_timestamp", &chdr_rfnoc::ctrl_payload::has_timestamp)
.def_readwrite("dst_port", &chdr_rfnoc::ctrl_payload::dst_port)
.def_readwrite("src_port", &chdr_rfnoc::ctrl_payload::src_port)
.def_readwrite("seq_num", &chdr_rfnoc::ctrl_payload::seq_num)
.def_readwrite("timestamp", &chdr_rfnoc::ctrl_payload::timestamp)
.def_readwrite("is_ack", &chdr_rfnoc::ctrl_payload::is_ack)
.def_readwrite("src_epid", &chdr_rfnoc::ctrl_payload::src_epid)
.def_readwrite("address", &chdr_rfnoc::ctrl_payload::address)
.def_readwrite("byte_enable", &chdr_rfnoc::ctrl_payload::byte_enable)
.def_readwrite("op_code", &chdr_rfnoc::ctrl_payload::op_code)
.def_readwrite("status", &chdr_rfnoc::ctrl_payload::status)
.def("get_data",
[](chdr_rfnoc::ctrl_payload& self) -> std::vector<uint32_t> {
return self.data_vtr;
})
.def("set_data",
[](chdr_rfnoc::ctrl_payload& self, std::vector<uint32_t> data) {
self.data_vtr = data;
})
.def("__str__", &chdr_rfnoc::ctrl_payload::to_string)
.def("__repr__", &chdr_rfnoc::ctrl_payload::to_string);
py::enum_<chdr_rfnoc::ctrl_status_t>(m, "CtrlStatus")
.value("OKAY", chdr_rfnoc::CMD_OKAY)
.value("CMDERR", chdr_rfnoc::CMD_CMDERR)
.value("TSERR", chdr_rfnoc::CMD_TSERR)
.value("WARNING", chdr_rfnoc::CMD_WARNING);
py::enum_<chdr_rfnoc::ctrl_opcode_t>(m, "CtrlOpCode")
.value("SLEEP", chdr_rfnoc::OP_SLEEP)
.value("WRITE", chdr_rfnoc::OP_WRITE)
.value("READ", chdr_rfnoc::OP_READ)
.value("READ_WRITE", chdr_rfnoc::OP_READ_WRITE)
.value("BLOCK_WRITE", chdr_rfnoc::OP_BLOCK_WRITE)
.value("BLOCK_READ", chdr_rfnoc::OP_BLOCK_READ)
.value("POLL", chdr_rfnoc::OP_POLL)
.value("USER1", chdr_rfnoc::OP_USER1)
.value("USER2", chdr_rfnoc::OP_USER2)
.value("USER3", chdr_rfnoc::OP_USER3)
.value("USER4", chdr_rfnoc::OP_USER4)
.value("USER5", chdr_rfnoc::OP_USER5)
.value("USER6", chdr_rfnoc::OP_USER6);
py::class_<chdr_rfnoc::mgmt_payload>(m, "MgmtPayload")
// Constructor
.def(py::init<>())
// Methods
.def("set_header",
&chdr_rfnoc::mgmt_payload::set_header,
py::arg("src_epid"),
py::arg("proto_ver"),
py::arg("chdr_w"))
.def("add_hop", &chdr_rfnoc::mgmt_payload::add_hop)
.def("get_num_hops", &chdr_rfnoc::mgmt_payload::get_num_hops)
.def("get_hop",
&chdr_rfnoc::mgmt_payload::get_hop,
py::return_value_policy::reference_internal)
.def("pop_hop", &chdr_rfnoc::mgmt_payload::pop_hop)
.def_property("src_epid",
&chdr_rfnoc::mgmt_payload::get_src_epid,
&chdr_rfnoc::mgmt_payload::set_src_epid)
.def_property("chdr_w",
&chdr_rfnoc::mgmt_payload::get_chdr_w,
&chdr_rfnoc::mgmt_payload::set_chdr_w)
.def_property("proto_ver",
&chdr_rfnoc::mgmt_payload::get_proto_ver,
&chdr_rfnoc::mgmt_payload::set_proto_ver)
.def("__str__", &chdr_rfnoc::mgmt_payload::to_string)
.def("__repr__", &chdr_rfnoc::mgmt_payload::to_string)
.def("hops_to_string", &chdr_rfnoc::mgmt_payload::hops_to_string);
py::class_<chdr_rfnoc::mgmt_hop_t>(m, "MgmtHop")
// Constructor
.def(py::init<>())
// Methods
.def("add_op", &chdr_rfnoc::mgmt_hop_t::add_op)
.def("get_num_ops", &chdr_rfnoc::mgmt_hop_t::get_num_ops)
.def("get_op",
&chdr_rfnoc::mgmt_hop_t::get_op,
py::return_value_policy::reference_internal)
.def("__str__", &chdr_rfnoc::mgmt_hop_t::to_string)
.def("__repr__", &chdr_rfnoc::mgmt_hop_t::to_string);
py::class_<chdr_rfnoc::mgmt_op_t>(m, "MgmtOp")
// Constructor
.def(py::init<chdr_rfnoc::mgmt_op_t::op_code_t, uint64_t>(),
py::arg("op_code"),
py::arg("op_payload") = 0)
.def(py::init<chdr_rfnoc::mgmt_op_t::op_code_t,
chdr_rfnoc::mgmt_op_t::sel_dest_payload>(),
py::arg("op_code"),
py::arg("op_payload"))
.def(py::init<chdr_rfnoc::mgmt_op_t::op_code_t,
chdr_rfnoc::mgmt_op_t::cfg_payload>(),
py::arg("op_code"),
py::arg("op_payload"))
.def(py::init<chdr_rfnoc::mgmt_op_t::op_code_t,
chdr_rfnoc::mgmt_op_t::node_info_payload>(),
py::arg("op_code"),
py::arg("op_payload"))
// Methods
.def_property_readonly("op_code", &chdr_rfnoc::mgmt_op_t::get_op_code)
.def("get_op_payload", &chdr_rfnoc::mgmt_op_t::get_op_payload)
.def("__str__", &chdr_rfnoc::mgmt_op_t::to_string)
.def("__repr__", &chdr_rfnoc::mgmt_op_t::to_string);
py::enum_<chdr_rfnoc::mgmt_op_t::op_code_t>(m, "MgmtOpCode")
.value("NOP", chdr_rfnoc::mgmt_op_t::MGMT_OP_NOP)
.value("ADVERTISE", chdr_rfnoc::mgmt_op_t::MGMT_OP_ADVERTISE)
.value("SEL_DEST", chdr_rfnoc::mgmt_op_t::MGMT_OP_SEL_DEST)
.value("RETURN", chdr_rfnoc::mgmt_op_t::MGMT_OP_RETURN)
.value("INFO_REQ", chdr_rfnoc::mgmt_op_t::MGMT_OP_INFO_REQ)
.value("INFO_RESP", chdr_rfnoc::mgmt_op_t::MGMT_OP_INFO_RESP)
.value("CFG_WR_REQ", chdr_rfnoc::mgmt_op_t::MGMT_OP_CFG_WR_REQ)
.value("CFG_RD_REQ", chdr_rfnoc::mgmt_op_t::MGMT_OP_CFG_RD_REQ)
.value("CFG_RD_RESP", chdr_rfnoc::mgmt_op_t::MGMT_OP_CFG_RD_RESP);
py::class_<chdr_rfnoc::mgmt_op_t::sel_dest_payload>(m, "MgmtOpSelDest")
.def(py::init<uint16_t>())
.def_readonly("dest", &chdr_rfnoc::mgmt_op_t::sel_dest_payload::dest)
.def_static(
"parse", [](uint64_t value) -> chdr_rfnoc::mgmt_op_t::sel_dest_payload {
return value;
});
py::class_<chdr_rfnoc::mgmt_op_t::cfg_payload>(m, "MgmtOpCfg")
.def(py::init<uint16_t, uint32_t>(), py::arg("addr"), py::arg("data"))
.def_readonly("addr", &chdr_rfnoc::mgmt_op_t::cfg_payload::addr)
.def_readonly("data", &chdr_rfnoc::mgmt_op_t::cfg_payload::data)
.def_static("parse",
[](uint64_t value) -> chdr_rfnoc::mgmt_op_t::cfg_payload { return value; });
py::class_<chdr_rfnoc::mgmt_op_t::node_info_payload>(m, "MgmtOpNodeInfo")
.def(py::init<uint16_t, uint8_t, uint16_t, uint32_t>(),
py::arg("device_id"),
py::arg("node_type"),
py::arg("node_inst"),
py::arg("ext_info"))
.def_readonly("device_id", &chdr_rfnoc::mgmt_op_t::node_info_payload::device_id)
.def_readonly("node_type", &chdr_rfnoc::mgmt_op_t::node_info_payload::node_type)
.def_readonly("node_inst", &chdr_rfnoc::mgmt_op_t::node_info_payload::node_inst)
.def_readonly("ext_info", &chdr_rfnoc::mgmt_op_t::node_info_payload::ext_info)
.def_static(
"parse", [](uint64_t value) -> chdr_rfnoc::mgmt_op_t::node_info_payload {
return value;
});
py::class_<chdr_rfnoc::strs_payload>(m, "StrsPayload")
// Constructor
.def(py::init<>())
// Methods
.def_readwrite("src_epid", &chdr_rfnoc::strs_payload::src_epid)
.def_readwrite("status", &chdr_rfnoc::strs_payload::status)
.def_readwrite("capacity_bytes", &chdr_rfnoc::strs_payload::capacity_bytes)
.def_readwrite("capacity_pkts", &chdr_rfnoc::strs_payload::capacity_pkts)
.def_readwrite("xfer_count_bytes", &chdr_rfnoc::strs_payload::xfer_count_bytes)
.def_readwrite("xfer_count_pkts", &chdr_rfnoc::strs_payload::xfer_count_pkts)
.def_readwrite("buff_info", &chdr_rfnoc::strs_payload::buff_info)
.def_readwrite("status_info", &chdr_rfnoc::strs_payload::status_info)
.def("__str__", &chdr_rfnoc::strs_payload::to_string)
.def("__repr__", &chdr_rfnoc::strs_payload::to_string);
py::enum_<chdr_rfnoc::strs_status_t>(m, "StrsStatus")
.value("OKAY", chdr_rfnoc::STRS_OKAY)
.value("CMDERR", chdr_rfnoc::STRS_CMDERR)
.value("SEQERR", chdr_rfnoc::STRS_SEQERR)
.value("DATAERR", chdr_rfnoc::STRS_DATAERR)
.value("RTERR", chdr_rfnoc::STRS_RTERR);
py::class_<chdr_rfnoc::strc_payload>(m, "StrcPayload")
// Constructor
.def(py::init<>())
// Methods
.def_readwrite("src_epid", &chdr_rfnoc::strc_payload::src_epid)
.def_readwrite("op_code", &chdr_rfnoc::strc_payload::op_code)
.def_readwrite("op_data", &chdr_rfnoc::strc_payload::op_data)
.def_readwrite("num_pkts", &chdr_rfnoc::strc_payload::num_pkts)
.def_readwrite("num_bytes", &chdr_rfnoc::strc_payload::num_bytes)
.def("__str__", &chdr_rfnoc::strc_payload::to_string)
.def("__repr__", &chdr_rfnoc::strc_payload::to_string);
py::enum_<chdr_rfnoc::strc_op_code_t>(m, "StrcOpCode")
.value("INIT", chdr_rfnoc::STRC_INIT)
.value("PING", chdr_rfnoc::STRC_PING)
.value("RESYNC", chdr_rfnoc::STRC_RESYNC);
}