mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
rfnoc: Increase Ctrl SeqNum to 8 bits
Increase the control SeqNum field width from 6 bits to 8 bits. See the updates to rfnoc_fpga.md for details.
This commit is contained in:
parent
8c2a20de50
commit
19d7d1a6a5
@ -260,7 +260,7 @@ public: // Members
|
||||
uint16_t dst_port = 0;
|
||||
//! Source port for transaction (10 bits)
|
||||
uint16_t src_port = 0;
|
||||
//! Sequence number (6 bits)
|
||||
//! Sequence number (8 bits)
|
||||
uint8_t seq_num = 0;
|
||||
//! Has Time Flag (1 bit) and timestamp (64 bits)
|
||||
std::optional<uint64_t> timestamp{};
|
||||
@ -272,9 +272,10 @@ public: // Members
|
||||
uint32_t address = 0;
|
||||
//! Data for transaction (vector of 32 bits)
|
||||
std::vector<uint32_t> data_vtr = {0};
|
||||
//! Number of 32-bit data words present in the packet, or the requested
|
||||
//! word count for read requests (4 bits)
|
||||
//! Number of 32-bit data words present in the packet (4 bits)
|
||||
size_t num_data = 1;
|
||||
//! Number of 32-bit data words requested (4 bits)
|
||||
size_t req_size = 0;
|
||||
//! Byte-enable mask for transaction (4 bits)
|
||||
uint8_t byte_enable = 0xF;
|
||||
//! Operation code (4 bits)
|
||||
@ -343,8 +344,9 @@ public: // Functions
|
||||
}
|
||||
|
||||
//! Returns true if this is a read request (OP_READ or OP_BLOCK_READ, not
|
||||
//! an ACK). For such packets, num_data in the header encodes the requested
|
||||
//! word count, but no data beats are transmitted on the wire.
|
||||
//! an ACK). For such packets, req_size in the header encodes the
|
||||
//! requested word count, and num_data is 0 since no data beats are
|
||||
//! transmitted on the wire.
|
||||
bool is_read_request() const
|
||||
{
|
||||
return !is_ack && (op_code == OP_READ || op_code == OP_BLOCK_READ);
|
||||
@ -383,7 +385,8 @@ private:
|
||||
static constexpr size_t DST_PORT_WIDTH = 10;
|
||||
static constexpr size_t SRC_PORT_WIDTH = 10;
|
||||
static constexpr size_t NUM_DATA_WIDTH = 4;
|
||||
static constexpr size_t SEQ_NUM_WIDTH = 6;
|
||||
static constexpr size_t REQ_SIZE_WIDTH = 4;
|
||||
static constexpr size_t SEQ_NUM_WIDTH = 8;
|
||||
static constexpr size_t HAS_TIME_WIDTH = 1;
|
||||
static constexpr size_t IS_ACK_WIDTH = 1;
|
||||
static constexpr size_t SRC_EPID_WIDTH = 16;
|
||||
@ -395,13 +398,14 @@ private:
|
||||
// Bit offsets within each 32-bit wire word
|
||||
// Ctrl header word 0
|
||||
static constexpr size_t DST_PORT_OFFSET = 0;
|
||||
static constexpr size_t SRC_PORT_OFFSET = 10;
|
||||
static constexpr size_t NUM_DATA_OFFSET = 20;
|
||||
static constexpr size_t SEQ_NUM_OFFSET = 24;
|
||||
static constexpr size_t HAS_TIME_OFFSET = 30;
|
||||
static constexpr size_t IS_ACK_OFFSET = 31;
|
||||
static constexpr size_t NUM_DATA_OFFSET = 10;
|
||||
static constexpr size_t HAS_TIME_OFFSET = 14;
|
||||
static constexpr size_t IS_ACK_OFFSET = 15;
|
||||
static constexpr size_t SRC_EPID_OFFSET = 16;
|
||||
// Ctrl header word 1
|
||||
static constexpr size_t SRC_EPID_OFFSET = 0;
|
||||
static constexpr size_t SRC_PORT_OFFSET = 0;
|
||||
static constexpr size_t SEQ_NUM_OFFSET = 20;
|
||||
static constexpr size_t REQ_SIZE_OFFSET = 28;
|
||||
// Op-word
|
||||
static constexpr size_t ADDRESS_OFFSET = 0;
|
||||
static constexpr size_t BYTE_ENABLE_OFFSET = 20;
|
||||
|
||||
@ -75,33 +75,33 @@ size_t ctrl_payload::serialize(uint32_t* buff,
|
||||
size_t max_size_bytes,
|
||||
const std::function<uint32_t(uint32_t)>& conv_byte_order) const
|
||||
{
|
||||
// Ctrl Packet NumData can be from 0 to 15. NumData = 0 is only valid for
|
||||
// write/sleep responses, which carry no data words. We only put data words
|
||||
// in write requests and read responses.
|
||||
UHD_ASSERT_THROW(
|
||||
num_data <= MAX_DATA_WORDS && (num_data >= 1 || is_write_response()));
|
||||
UHD_ASSERT_THROW((is_read_request() || is_write_response())
|
||||
? data_vtr.empty()
|
||||
: (!data_vtr.empty() && data_vtr.size() <= MAX_DATA_WORDS));
|
||||
// NumData is the number of 32-bit data words actually present in the
|
||||
// packet and must match the data vector. ReqSize is the requested word
|
||||
// count (read requests only, echoed in the response) and is independent of
|
||||
// the data words present.
|
||||
UHD_ASSERT_THROW(num_data == data_vtr.size() && num_data <= MAX_DATA_WORDS);
|
||||
UHD_ASSERT_THROW(req_size <= MAX_DATA_WORDS);
|
||||
UHD_ASSERT_THROW(get_length() <= max_size_bytes);
|
||||
size_t ptr = 0;
|
||||
|
||||
// Populate control header word 0: dst_port, src_port, num_data, seq_num,
|
||||
// has_time, is_ack
|
||||
// Populate control header word 0: src_epid, is_ack, has_time, num_data,
|
||||
// dst_port
|
||||
buff[ptr++] = conv_byte_order(
|
||||
((static_cast<uint32_t>(dst_port) & mask_u32(DST_PORT_WIDTH)) << DST_PORT_OFFSET)
|
||||
| ((static_cast<uint32_t>(src_port) & mask_u32(SRC_PORT_WIDTH))
|
||||
<< SRC_PORT_OFFSET)
|
||||
| ((static_cast<uint32_t>(num_data) & mask_u32(NUM_DATA_WIDTH))
|
||||
<< NUM_DATA_OFFSET)
|
||||
| ((static_cast<uint32_t>(seq_num) & mask_u32(SEQ_NUM_WIDTH)) << SEQ_NUM_OFFSET)
|
||||
| ((static_cast<uint32_t>(bool(timestamp) ? 1 : 0) & mask_u32(HAS_TIME_WIDTH))
|
||||
<< HAS_TIME_OFFSET)
|
||||
| ((static_cast<uint32_t>(is_ack) & mask_u32(IS_ACK_WIDTH)) << IS_ACK_OFFSET));
|
||||
| ((static_cast<uint32_t>(is_ack) & mask_u32(IS_ACK_WIDTH)) << IS_ACK_OFFSET)
|
||||
| ((static_cast<uint32_t>(src_epid) & mask_u32(SRC_EPID_WIDTH))
|
||||
<< SRC_EPID_OFFSET));
|
||||
|
||||
// Populate control header word 1: src_epid
|
||||
buff[ptr++] =
|
||||
conv_byte_order(static_cast<uint32_t>(src_epid) & mask_u32(SRC_EPID_WIDTH));
|
||||
// Populate control header word 1: req_size, seq_num, src_port
|
||||
buff[ptr++] = conv_byte_order(
|
||||
((static_cast<uint32_t>(src_port) & mask_u32(SRC_PORT_WIDTH)) << SRC_PORT_OFFSET)
|
||||
| ((static_cast<uint32_t>(seq_num) & mask_u32(SEQ_NUM_WIDTH)) << SEQ_NUM_OFFSET)
|
||||
| ((static_cast<uint32_t>(req_size) & mask_u32(REQ_SIZE_WIDTH))
|
||||
<< REQ_SIZE_OFFSET));
|
||||
|
||||
// Populate optional timestamp (low word first, then high word)
|
||||
if (bool(timestamp)) {
|
||||
@ -118,11 +118,10 @@ size_t ctrl_payload::serialize(uint32_t* buff,
|
||||
| ((static_cast<uint32_t>(op_code) & mask_u32(OPCODE_WIDTH)) << OPCODE_OFFSET)
|
||||
| ((static_cast<uint32_t>(status) & mask_u32(STATUS_WIDTH)) << STATUS_OFFSET));
|
||||
|
||||
// For read requests and write/sleep responses, no data words follow.
|
||||
if (!is_read_request() && !is_write_response()) {
|
||||
for (size_t word_idx = 0; word_idx < data_vtr.size(); word_idx++) {
|
||||
buff[ptr++] = conv_byte_order(data_vtr[word_idx]);
|
||||
}
|
||||
// Append any data words present in the packet. NumData (and thus
|
||||
// data_vtr) is empty for read requests and write/sleep responses.
|
||||
for (size_t word_idx = 0; word_idx < data_vtr.size(); word_idx++) {
|
||||
buff[ptr++] = conv_byte_order(data_vtr[word_idx]);
|
||||
}
|
||||
|
||||
UHD_ASSERT_THROW(ptr * sizeof(uint32_t) == get_length());
|
||||
@ -138,22 +137,23 @@ void ctrl_payload::deserialize(const uint32_t* buff,
|
||||
// Minimum: ctrl header (2 words) + op-word (1 word)
|
||||
UHD_ASSERT_THROW(buff_size >= 3);
|
||||
|
||||
// Read control header word 0: dst_port, src_port, num_data, seq_num,
|
||||
// has_time, is_ack
|
||||
// Read control header word 0: src_epid, is_ack, has_time, num_data,
|
||||
// dst_port
|
||||
const uint32_t hdr_lo = conv_byte_order(buff[ptr++]);
|
||||
const size_t num_data_from_hdr =
|
||||
get_field_u32<size_t>(hdr_lo, NUM_DATA_OFFSET, NUM_DATA_WIDTH);
|
||||
UHD_ASSERT_THROW(num_data_from_hdr <= MAX_DATA_WORDS);
|
||||
num_data = num_data_from_hdr;
|
||||
dst_port = get_field_u32<uint16_t>(hdr_lo, DST_PORT_OFFSET, DST_PORT_WIDTH);
|
||||
src_port = get_field_u32<uint16_t>(hdr_lo, SRC_PORT_OFFSET, SRC_PORT_WIDTH);
|
||||
seq_num = get_field_u32<uint8_t>(hdr_lo, SEQ_NUM_OFFSET, SEQ_NUM_WIDTH);
|
||||
src_epid = get_field_u32<uint16_t>(hdr_lo, SRC_EPID_OFFSET, SRC_EPID_WIDTH);
|
||||
const bool has_time = get_field_u32<bool>(hdr_lo, HAS_TIME_OFFSET, HAS_TIME_WIDTH);
|
||||
is_ack = get_field_u32<bool>(hdr_lo, IS_ACK_OFFSET, IS_ACK_WIDTH);
|
||||
|
||||
// Read control header word 1: src_epid
|
||||
// Read control header word 1: req_size, seq_num, src_port
|
||||
const uint32_t hdr_hi = conv_byte_order(buff[ptr++]);
|
||||
src_epid = get_field_u32<uint16_t>(hdr_hi, SRC_EPID_OFFSET, SRC_EPID_WIDTH);
|
||||
src_port = get_field_u32<uint16_t>(hdr_hi, SRC_PORT_OFFSET, SRC_PORT_WIDTH);
|
||||
seq_num = get_field_u32<uint8_t>(hdr_hi, SEQ_NUM_OFFSET, SEQ_NUM_WIDTH);
|
||||
req_size = get_field_u32<size_t>(hdr_hi, REQ_SIZE_OFFSET, REQ_SIZE_WIDTH);
|
||||
|
||||
// Read optional timestamp (low word first, then high word)
|
||||
if (has_time) {
|
||||
@ -173,23 +173,10 @@ void ctrl_payload::deserialize(const uint32_t* buff,
|
||||
op_code = get_field_u32<ctrl_opcode_t>(op_lo, OPCODE_OFFSET, OPCODE_WIDTH);
|
||||
status = get_field_u32<ctrl_status_t>(op_lo, STATUS_OFFSET, STATUS_WIDTH);
|
||||
|
||||
// NumData = 0 is only valid for write/sleep responses, which carry no data
|
||||
// words. All other packets must have NumData >= 1.
|
||||
UHD_ASSERT_THROW(num_data >= 1 || is_write_response());
|
||||
|
||||
// For read requests, num_data encodes the requested word count. No data follows.
|
||||
if (is_read_request()) {
|
||||
data_vtr.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write and sleep ACKs carry no data words.
|
||||
if (is_write_response()) {
|
||||
data_vtr.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read data words
|
||||
// NumData is the number of data words actually present in the packet. Read
|
||||
// the data words that follow the op-word, bounded by the available buffer
|
||||
// to tolerate trailing padding (e.g. on wide CHDR buses). NumData is 0 for
|
||||
// read requests and write/sleep responses, so no data words are read.
|
||||
const size_t num_data_present = buff_size - ptr;
|
||||
const size_t actual_words = std::min(num_data_present, num_data_from_hdr);
|
||||
data_vtr.resize(actual_words);
|
||||
@ -197,11 +184,6 @@ void ctrl_payload::deserialize(const uint32_t* buff,
|
||||
data_vtr[word_idx] = conv_byte_order(buff[ptr++]);
|
||||
}
|
||||
|
||||
// For read responses, the word count must equal the requested count
|
||||
if (is_read_response()) {
|
||||
UHD_ASSERT_THROW(data_vtr.size() == num_data_from_hdr);
|
||||
}
|
||||
|
||||
UHD_ASSERT_THROW(ptr <= buff_size);
|
||||
}
|
||||
|
||||
@ -210,16 +192,13 @@ size_t ctrl_payload::get_length() const
|
||||
// Control packets are always a multiple of 32 bits (4 bytes). The control
|
||||
// header is 64 bits (2 words), timestamp is 64 bits (2 words) if present,
|
||||
// the op-word is 32 bits (1 word), and data words are 32 bits each. Read
|
||||
// requests and write/sleep responses carry no data words on the wire.
|
||||
// requests and write/sleep responses carry no data words on the wire
|
||||
// (data_vtr is empty).
|
||||
size_t num_32bit_words = 2; // ctrl header = 64 bits = two 32-bit words
|
||||
if (this->has_timestamp()) {
|
||||
num_32bit_words += 2; // timestamp = 64 bits
|
||||
}
|
||||
if (is_read_request() || is_write_response()) {
|
||||
num_32bit_words += 1; // op-word only
|
||||
} else {
|
||||
num_32bit_words += 1 + this->data_vtr.size(); // op-word + data
|
||||
}
|
||||
num_32bit_words += 1 + this->data_vtr.size(); // op-word + data
|
||||
return num_32bit_words * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
@ -230,7 +209,8 @@ bool ctrl_payload::operator==(const ctrl_payload& rhs) const
|
||||
&& (is_ack == rhs.is_ack) && (src_epid == rhs.src_epid)
|
||||
&& (address == rhs.address) && (data_vtr == rhs.data_vtr)
|
||||
&& (byte_enable == rhs.byte_enable) && (op_code == rhs.op_code)
|
||||
&& (status == rhs.status) && (num_data == rhs.num_data);
|
||||
&& (status == rhs.status) && (num_data == rhs.num_data)
|
||||
&& (req_size == rhs.req_size);
|
||||
}
|
||||
|
||||
std::string ctrl_payload::to_string() const
|
||||
@ -238,12 +218,12 @@ std::string ctrl_payload::to_string() const
|
||||
return str(boost::format(
|
||||
"ctrl_payload{dst_port:%d, src_port:%d, seq_num:%d, timestamp:%s, "
|
||||
"is_ack:%s, src_epid:%d, address:0x%05x, byte_enable:0x%x, "
|
||||
"op_code:%d, status:%d, num_data:%d")
|
||||
"op_code:%d, status:%d, num_data:%d, req_size:%d")
|
||||
% dst_port % src_port % int(seq_num)
|
||||
% (bool(timestamp) ? str(boost::format("0x%016x") % *timestamp)
|
||||
: std::string("<not present>"))
|
||||
% (is_ack ? "true" : "false") % src_epid % address % int(byte_enable)
|
||||
% op_code % status % int(num_data))
|
||||
% op_code % status % int(num_data) % int(req_size))
|
||||
+
|
||||
[&]() {
|
||||
std::ostringstream oss;
|
||||
|
||||
@ -35,10 +35,8 @@ constexpr double DEFAULT_TIMEOUT = 1.0;
|
||||
constexpr double MASSIVE_TIMEOUT = 10.0;
|
||||
//! Default value for whether ACKs are always required
|
||||
constexpr bool DEFAULT_FORCE_ACKS = false;
|
||||
//! Sequence numbers are 6 bits long
|
||||
constexpr uint8_t SEQ_NUM_MASK = 0b111111; // 0x3F
|
||||
//! Sequence number limit for control packets.
|
||||
constexpr uint8_t MAX_SEQ_NUM = SEQ_NUM_MASK + 1;
|
||||
//! Sequence numbers are 8 bits long
|
||||
constexpr uint8_t SEQ_NUM_MASK = 0b11111111; // 0xFF
|
||||
} // namespace
|
||||
|
||||
|
||||
@ -254,8 +252,8 @@ public:
|
||||
resp_status = RESP_SIZEERR;
|
||||
}
|
||||
} else if (is_read_op) {
|
||||
// Read responses must return exactly num_data words.
|
||||
if (rx_ctrl.data_vtr.size() != _req_queue.front().num_data) {
|
||||
// Read responses must return exactly req_size words.
|
||||
if (rx_ctrl.data_vtr.size() != _req_queue.front().req_size) {
|
||||
resp_status = RESP_SIZEERR;
|
||||
}
|
||||
} else if (is_sleep_op) {
|
||||
@ -319,16 +317,18 @@ public:
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
// Peek at the request queue to check the expected sequence number.
|
||||
// If the request queue is empty, then we always have an error so
|
||||
// we simply set a big value in seq_num_diff.
|
||||
// we simply set a non-zero sentinel in seq_num_diff.
|
||||
// Note: with 8-bit seq_nums, uint8_t subtraction wraps mod 256
|
||||
// naturally, so no explicit modulo is needed.
|
||||
const uint8_t seq_num_diff =
|
||||
_req_queue.empty()
|
||||
? MAX_SEQ_NUM
|
||||
: uint8_t(rx_ctrl.seq_num - _req_queue.front().seq_num) % MAX_SEQ_NUM;
|
||||
? uint8_t(1)
|
||||
: uint8_t(rx_ctrl.seq_num - _req_queue.front().seq_num);
|
||||
if (seq_num_diff == 0) { // No sequence error by seq_num
|
||||
// Also verify op_code and address to guard against stale ACKs
|
||||
// that happen to share the same 6-bit seq_num (either from the
|
||||
// that happen to share the same 8-bit seq_num (either from the
|
||||
// previous session or from earlier in the current session when
|
||||
// the 6-bit counter wraps). A genuine ACK always echoes op_code
|
||||
// the 8-bit counter wraps). A genuine ACK always echoes op_code
|
||||
// and address unchanged.
|
||||
const bool op_addr_match = _req_queue.front().op_code == rx_ctrl.op_code
|
||||
&& _req_queue.front().address
|
||||
@ -410,6 +410,7 @@ public:
|
||||
tx_ctrl.src_epid = _my_epid;
|
||||
tx_ctrl.status = status;
|
||||
tx_ctrl.data_vtr = {};
|
||||
tx_ctrl.num_data = 0;
|
||||
const auto timeout = [&]() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
return _policy.timeout;
|
||||
@ -534,7 +535,7 @@ private:
|
||||
const std::vector<uint32_t>& data_vtr,
|
||||
const uhd::time_spec_t& time_spec,
|
||||
const bool require_ack,
|
||||
const size_t num_data = 0) // 0 = derive from data_vtr.size()
|
||||
const size_t req_size = 0)
|
||||
{
|
||||
if (!_client_clk.is_running()) {
|
||||
UHD_LOG_THROW(
|
||||
@ -563,7 +564,8 @@ private:
|
||||
tx_ctrl.src_epid = _my_epid;
|
||||
tx_ctrl.address = address;
|
||||
tx_ctrl.data_vtr = data_vtr;
|
||||
tx_ctrl.num_data = (num_data != 0) ? num_data : data_vtr.size();
|
||||
tx_ctrl.num_data = data_vtr.size();
|
||||
tx_ctrl.req_size = req_size;
|
||||
tx_ctrl.byte_enable = 0xF;
|
||||
tx_ctrl.op_code = op_code;
|
||||
tx_ctrl.status = CMD_OKAY;
|
||||
@ -638,10 +640,10 @@ private:
|
||||
const std::vector<uint32_t>& data_vtr,
|
||||
const uhd::time_spec_t& time_spec,
|
||||
const bool require_ack = true,
|
||||
const size_t num_data = 0) // 0 = derive from data_vtr.size()
|
||||
const size_t req_size = 0)
|
||||
{
|
||||
const auto [tx_ctrl, registered] = fire_request_packet(
|
||||
op_code, address, data_vtr, time_spec, require_ack, num_data);
|
||||
op_code, address, data_vtr, time_spec, require_ack, req_size);
|
||||
if (registered) {
|
||||
return {tx_ctrl, collect_ack_response(tx_ctrl)};
|
||||
}
|
||||
|
||||
@ -182,6 +182,7 @@ void export_utils(py::module& m)
|
||||
.def_readwrite("src_epid", &chdr_rfnoc::ctrl_payload::src_epid)
|
||||
.def_readwrite("address", &chdr_rfnoc::ctrl_payload::address)
|
||||
.def_readwrite("num_data", &chdr_rfnoc::ctrl_payload::num_data)
|
||||
.def_readwrite("req_size", &chdr_rfnoc::ctrl_payload::req_size)
|
||||
.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)
|
||||
@ -192,6 +193,7 @@ void export_utils(py::module& m)
|
||||
.def("set_data",
|
||||
[](chdr_rfnoc::ctrl_payload& self, std::vector<uint32_t> data) {
|
||||
self.data_vtr = data;
|
||||
self.num_data = data.size();
|
||||
})
|
||||
.def("__str__", &chdr_rfnoc::ctrl_payload::to_string)
|
||||
.def("__repr__", &chdr_rfnoc::ctrl_payload::to_string);
|
||||
|
||||
@ -25,6 +25,8 @@ chdr_util::chdr_packet make_control_packet0()
|
||||
auto payload = chdr_rfnoc::ctrl_payload();
|
||||
payload.src_epid = 1;
|
||||
payload.data_vtr = {};
|
||||
payload.num_data = 0;
|
||||
payload.req_size = 1;
|
||||
payload.op_code = chdr_rfnoc::OP_READ;
|
||||
return chdr_util::chdr_packet(CHDR_W, header, payload);
|
||||
}
|
||||
@ -39,6 +41,8 @@ chdr_util::chdr_packet make_control_packet1()
|
||||
payload.src_epid = 2;
|
||||
payload.is_ack = true;
|
||||
payload.data_vtr = {0x12C60100};
|
||||
payload.num_data = 1;
|
||||
payload.req_size = 1;
|
||||
payload.op_code = chdr_rfnoc::OP_READ;
|
||||
return chdr_util::chdr_packet(CHDR_W, header, payload);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ def make_control_packet0():
|
||||
payload = chdr.CtrlPayload()
|
||||
payload.src_epid = 1
|
||||
payload.set_data([])
|
||||
payload.req_size = 1
|
||||
payload.op_code = chdr.CtrlOpCode.READ
|
||||
return chdr.ChdrPacket(CHDR_W, header, payload)
|
||||
|
||||
@ -29,6 +30,7 @@ def make_control_packet1():
|
||||
payload.src_epid = 2
|
||||
payload.is_ack = True
|
||||
payload.set_data([0x12C60100])
|
||||
payload.req_size = 1
|
||||
payload.op_code = chdr.CtrlOpCode.READ
|
||||
return chdr.ChdrPacket(CHDR_W, header, payload)
|
||||
|
||||
@ -148,7 +150,7 @@ def make_data_packet0():
|
||||
header.seq_num = 1
|
||||
timestamp = 0x7C40C83
|
||||
data_src = rfnoc_packets_data.peer1_9
|
||||
data = bytes(data_src[(2 * 8):])
|
||||
data = bytes(data_src[(2 * 8) :])
|
||||
return chdr.ChdrPacket(CHDR_W, header, data, timestamp)
|
||||
|
||||
def make_data_packet1():
|
||||
@ -160,7 +162,7 @@ def make_data_packet1():
|
||||
header.seq_num = 1716
|
||||
timestamp = 0x21452B97
|
||||
data_src = rfnoc_packets_data.eob_packet
|
||||
data = bytes(data_src[(2 * 8):])
|
||||
data = bytes(data_src[(2 * 8) :])
|
||||
return chdr.ChdrPacket(CHDR_W, header, data, timestamp)
|
||||
|
||||
packets = [
|
||||
@ -174,7 +176,7 @@ packets = [
|
||||
(make_strc_packet0(), rfnoc_packets_data.peer1_5),
|
||||
(make_strc_packet1(), rfnoc_packets_data.peer1_29),
|
||||
(make_data_packet0(), rfnoc_packets_data.peer1_9),
|
||||
(make_data_packet1(), rfnoc_packets_data.eob_packet)
|
||||
(make_data_packet1(), rfnoc_packets_data.eob_packet),
|
||||
]
|
||||
|
||||
names = [
|
||||
@ -188,5 +190,5 @@ names = [
|
||||
"Stream_Command_Packet_0",
|
||||
"Stream_Command_Packet_1",
|
||||
"Data_Packet_0",
|
||||
"Data_Packet_1"
|
||||
"Data_Packet_1",
|
||||
]
|
||||
|
||||
@ -230,11 +230,11 @@ uint8_t peer1_16[] = { // Packet 3147
|
||||
0x00, 0x05, 0x03, 0x00, 0x02, 0xc0, 0x41, 0x10 };
|
||||
uint8_t peer0_19[] = { // Packet 3148
|
||||
0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0xf0, 0x02 };
|
||||
uint8_t peer1_17[] = { // Packet 3149
|
||||
0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x10, 0x80, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0xf0, 0x02, 0x00, 0x01, 0xc6, 0x12 };
|
||||
uint8_t peer0_20[] = { // Packet 3150
|
||||
0x02, 0x00, 0x18, 0x00, 0x01, 0x00, 0x80, 0x00,
|
||||
|
||||
@ -223,11 +223,11 @@ peer1_16 = bytes([ # Packet 3147
|
||||
0x00, 0x05, 0x03, 0x00, 0x02, 0xc0, 0x41, 0x10 ])
|
||||
peer0_19 = bytes([ # Packet 3148
|
||||
0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0xf0, 0x02 ])
|
||||
peer1_17 = bytes([ # Packet 3149
|
||||
0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x00, 0x00, 0x10, 0x80, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x84, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0xf0, 0x02, 0x00, 0x01, 0xc6, 0x12 ])
|
||||
peer0_20 = bytes([ # Packet 3150
|
||||
0x02, 0x00, 0x18, 0x00, 0x01, 0x00, 0x80, 0x00,
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include <condition_variable>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
@ -48,6 +49,13 @@ public:
|
||||
{
|
||||
// Create send function that captures packets
|
||||
send_fn = [this](const ctrl_payload& pkt, double timeout) {
|
||||
// Exercise payload serialization at the mock transport boundary.
|
||||
// This catches malformed packets that packet capture alone would
|
||||
// not detect, such as when num_data does not match
|
||||
// data_vtr.size().
|
||||
std::array<uint32_t, 20> scratch;
|
||||
pkt.serialize(scratch.data(), sizeof(scratch), [](uint32_t x) { return x; });
|
||||
|
||||
std::lock_guard<std::mutex> lock(sent_packets_mutex);
|
||||
sent_packets.push_back(pkt);
|
||||
last_timeout = timeout;
|
||||
@ -181,7 +189,8 @@ private:
|
||||
ctrl_payload response;
|
||||
response.dst_port = request.src_port;
|
||||
response.src_port = request.dst_port;
|
||||
response.num_data = request.num_data;
|
||||
response.num_data = request.req_size;
|
||||
response.req_size = request.req_size;
|
||||
response.seq_num = request.seq_num;
|
||||
response.timestamp = request.timestamp;
|
||||
response.is_ack = true;
|
||||
@ -191,7 +200,7 @@ private:
|
||||
response.status = CMD_OKAY;
|
||||
// Echo back the address as the data for predictable testing
|
||||
response.data_vtr.clear();
|
||||
for (size_t i = 0; i < request.num_data; i++) {
|
||||
for (size_t i = 0; i < request.req_size; i++) {
|
||||
uint32_t addr = request.address + static_cast<uint32_t>(i * sizeof(uint32_t));
|
||||
response.data_vtr.push_back(static_cast<uint32_t>(addr + 0xDEADBEEF));
|
||||
}
|
||||
@ -206,6 +215,7 @@ private:
|
||||
response.dst_port = request.src_port;
|
||||
response.src_port = request.dst_port;
|
||||
response.num_data = 0;
|
||||
response.req_size = request.req_size;
|
||||
response.seq_num = request.seq_num;
|
||||
response.timestamp = request.timestamp;
|
||||
response.is_ack = true;
|
||||
@ -359,7 +369,8 @@ BOOST_FIXTURE_TEST_CASE(test_peek32_basic, ctrlport_endpoint_fixture)
|
||||
auto packet = get_last_sent_packet();
|
||||
BOOST_CHECK_EQUAL(packet.op_code, OP_READ);
|
||||
BOOST_CHECK_EQUAL(packet.address, test_addr);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 1);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 0);
|
||||
BOOST_CHECK_EQUAL(packet.req_size, 1);
|
||||
BOOST_CHECK(packet.data_vtr.empty());
|
||||
BOOST_CHECK_EQUAL(result, test_addr + 0xDEADBEEF);
|
||||
}
|
||||
@ -378,7 +389,8 @@ BOOST_FIXTURE_TEST_CASE(test_peek32_with_timestamp, ctrlport_endpoint_fixture)
|
||||
auto packet = get_last_sent_packet();
|
||||
BOOST_CHECK_EQUAL(packet.op_code, OP_READ);
|
||||
BOOST_CHECK_EQUAL(packet.address, test_addr);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 1);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 0);
|
||||
BOOST_CHECK_EQUAL(packet.req_size, 1);
|
||||
BOOST_CHECK(packet.data_vtr.empty());
|
||||
BOOST_CHECK(packet.has_timestamp());
|
||||
|
||||
@ -454,7 +466,8 @@ BOOST_FIXTURE_TEST_CASE(test_block_peek32, ctrlport_endpoint_fixture)
|
||||
auto packet = get_last_sent_packet();
|
||||
BOOST_CHECK_EQUAL(packet.op_code, OP_BLOCK_READ);
|
||||
BOOST_CHECK_EQUAL(packet.address, base_addr);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, length);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 0);
|
||||
BOOST_CHECK_EQUAL(packet.req_size, length);
|
||||
BOOST_CHECK(packet.data_vtr.empty()); // No data in read request
|
||||
|
||||
// Verify result vector has correct size
|
||||
@ -547,7 +560,8 @@ BOOST_FIXTURE_TEST_CASE(test_burst_peek32, ctrlport_endpoint_fixture)
|
||||
auto packet = get_last_sent_packet();
|
||||
BOOST_CHECK_EQUAL(packet.op_code, OP_READ);
|
||||
BOOST_CHECK_EQUAL(packet.address, addr);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, length);
|
||||
BOOST_CHECK_EQUAL(packet.num_data, 0);
|
||||
BOOST_CHECK_EQUAL(packet.req_size, length);
|
||||
BOOST_CHECK(packet.data_vtr.empty()); // No data in read request
|
||||
|
||||
// Verify result vector has correct size
|
||||
@ -703,7 +717,7 @@ BOOST_FIXTURE_TEST_CASE(test_dropped_ack_handling, small_buffer_fixture)
|
||||
|
||||
// Add a bunch of pokes to bring the sequence number back around,
|
||||
// ack them all for now
|
||||
for (i = 0; i < 60; i++) {
|
||||
for (i = 0; i < 252; i++) {
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i);
|
||||
send_ack(get_last_sent_packet());
|
||||
// Synchronize ACKs so they all get delivered in order. This is so we
|
||||
@ -712,39 +726,39 @@ BOOST_FIXTURE_TEST_CASE(test_dropped_ack_handling, small_buffer_fixture)
|
||||
}
|
||||
|
||||
wait_for_all_responses();
|
||||
BOOST_CHECK_EQUAL(get_sent_packet_count(), 63); // 3 from before + 60 new ones
|
||||
BOOST_CHECK_EQUAL(get_sent_packet_count(), 255); // 3 from before + 252 new ones
|
||||
BOOST_CHECK_EQUAL(endpoint->get_stats().buffer_fullness, 0);
|
||||
BOOST_CHECK_EQUAL(get_last_sent_packet().seq_num, 62);
|
||||
BOOST_CHECK_EQUAL(get_last_sent_packet().seq_num, 254);
|
||||
|
||||
// Send two more pokes to bring the counter around, but drop the first ACK
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i); // seqnum 63
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i); // seqnum 255
|
||||
i++;
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i); // seqnum 0
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i); // seqnum 0 (8-bit wrap)
|
||||
i++;
|
||||
// Sequence number should have wrapped around to 0
|
||||
BOOST_CHECK_EQUAL(get_last_sent_packet().seq_num, 0);
|
||||
// ACK sequence number 0, but not 63
|
||||
// ACK sequence number 0, but not 255
|
||||
send_ack(get_last_sent_packet()); // ACK the last one, but not the previous one
|
||||
wait_for_all_responses();
|
||||
BOOST_CHECK_EQUAL(endpoint->get_stats().buffer_fullness, 0);
|
||||
BOOST_CHECK_EQUAL(endpoint->get_stats().ctrl_dropped, 2);
|
||||
|
||||
// Now ack the seqnum 63 packet. This is too late, and the response queue
|
||||
// Now ack the seqnum 255 packet. This is too late, and the response queue
|
||||
// should be empty, so we get a warning.
|
||||
auto seqnum_63_pkt = sent_packets[sent_packets.size() - 2];
|
||||
send_ack(seqnum_63_pkt);
|
||||
auto seqnum_255_pkt = sent_packets[sent_packets.size() - 2];
|
||||
send_ack(seqnum_255_pkt);
|
||||
wait_for_all_responses();
|
||||
|
||||
// Now fill the request queue again, so our algorithm has something to do
|
||||
endpoint->poke32(test_addr + (i * 4), test_data + i); // seqnum 1
|
||||
i++;
|
||||
|
||||
// Now ack the seqnum 63 packet again. This will produce the same warning
|
||||
// Now ack the seqnum 255 packet again. This will produce the same warning
|
||||
// again
|
||||
send_ack(seqnum_63_pkt);
|
||||
send_ack(seqnum_255_pkt);
|
||||
wait_for_all_responses();
|
||||
|
||||
BOOST_CHECK_EQUAL(endpoint->get_stats().ack_packets_received, 65);
|
||||
BOOST_CHECK_EQUAL(endpoint->get_stats().ack_packets_received, 257);
|
||||
}
|
||||
|
||||
// Verify that a stale ACK with matching seq_num but wrong op_code/address is
|
||||
|
||||
@ -45,12 +45,17 @@ ctrl_payload populate_ctrl_payload()
|
||||
} else {
|
||||
pyld.timestamp = {};
|
||||
}
|
||||
// Set num_data and data_vtr based on packet type.
|
||||
// Read requests and write responses carry no data words on the wire.
|
||||
// For read requests, num_data encodes the requested word count.
|
||||
if (pyld.is_read_request() || pyld.is_write_response()) {
|
||||
// Set num_data, req_size, and data_vtr based on packet type.
|
||||
// Read requests and write/sleep responses carry no data words on the wire.
|
||||
// For read requests, req_size encodes the requested word count.
|
||||
if (pyld.is_read_request()) {
|
||||
pyld.data_vtr = {};
|
||||
pyld.num_data = (rand64() % 15) + 1; // 1 to 15
|
||||
pyld.num_data = 0;
|
||||
pyld.req_size = (rand64() % 15) + 1; // 1 to 15
|
||||
} else if (pyld.is_write_response()) {
|
||||
pyld.data_vtr = {};
|
||||
pyld.num_data = 0;
|
||||
pyld.req_size = 0;
|
||||
} else {
|
||||
const size_t num_words = (rand64() % ctrl_payload::MAX_DATA_WORDS) + 1;
|
||||
pyld.data_vtr.resize(num_words);
|
||||
@ -58,6 +63,7 @@ ctrl_payload populate_ctrl_payload()
|
||||
word = rand64() & 0xFFFFFFFF;
|
||||
}
|
||||
pyld.num_data = pyld.data_vtr.size();
|
||||
pyld.req_size = 0;
|
||||
}
|
||||
return pyld;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user