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:
Wade Fife 2026-07-20 13:16:23 -05:00 committed by mkoop-ni
parent 8c2a20de50
commit 19d7d1a6a5
10 changed files with 131 additions and 117 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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)};
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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",
]

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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;
}