For example, the ChdrPacket typedef is being renamed from ChdrPacket to ChdrPacket_t. This allows the code to distinguish between the unparameterized class and the already parameterized class. This isn't strictly necessary, but it makes some Vivado 2019.1 bugs easier to work around. It also makes the code slightly less ambiguous. Original-commit: df27e9fa05d2d767f69182763852f6803c5b2a34
809 lines
28 KiB
Systemverilog
809 lines
28 KiB
Systemverilog
//
|
|
// Copyright 2019 Ettus Research, A National Instruments Company
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: PkgChdrBfm
|
|
//
|
|
// Description: Package for a bi-directional CHDR bus functional model (BFM),
|
|
// which consists primarily of the ChdrPacket and ChdrBfm classes.
|
|
//
|
|
|
|
|
|
|
|
package PkgChdrBfm;
|
|
|
|
import PkgChdrUtils::*;
|
|
import PkgAxiStreamBfm::*;
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// CHDR Packet Class
|
|
//---------------------------------------------------------------------------
|
|
|
|
class ChdrPacket #(int CHDR_W = 64);
|
|
|
|
typedef ChdrPacket #(CHDR_W) ChdrPacket_t;
|
|
typedef ChdrData #(CHDR_W)::chdr_word_t chdr_word_t;
|
|
|
|
const int BYTES_PER_CHDR_W = CHDR_W / 8;
|
|
|
|
chdr_header_t header;
|
|
chdr_timestamp_t timestamp;
|
|
chdr_word_t metadata[$];
|
|
chdr_word_t data[$];
|
|
|
|
extern function ChdrPacket_t copy();
|
|
extern function bit equal(ChdrPacket_t packet);
|
|
extern function string sprint(bit pretty = 1);
|
|
extern function void print(bit pretty = 1);
|
|
|
|
// Accessors
|
|
extern function void write_raw (ref chdr_header_t header,
|
|
ref chdr_word_t data[$],
|
|
input chdr_word_t metadata[$] = {},
|
|
input chdr_timestamp_t timestamp = 0,
|
|
input int data_byte_length = -1);
|
|
extern function void read_raw (output chdr_header_t header,
|
|
output chdr_word_t data[$],
|
|
output chdr_word_t metadata[$],
|
|
output chdr_timestamp_t timestamp,
|
|
output int data_byte_length);
|
|
extern function void write_stream_status(ref chdr_header_t header,
|
|
ref chdr_str_status_t status);
|
|
extern function void read_stream_status (output chdr_header_t header,
|
|
output chdr_str_status_t status);
|
|
extern function void write_stream_cmd (ref chdr_header_t header,
|
|
ref chdr_str_command_t command);
|
|
extern function void read_stream_cmd (output chdr_header_t header,
|
|
output chdr_str_command_t command);
|
|
extern function void write_mgmt (ref chdr_header_t header,
|
|
ref chdr_mgmt_t mgmt);
|
|
extern function void read_mgmt (output chdr_header_t header,
|
|
output chdr_mgmt_t mgmt);
|
|
extern function void write_ctrl (ref chdr_header_t header,
|
|
ref chdr_ctrl_header_t ctrl_header,
|
|
ref ctrl_op_word_t ctrl_op_word,
|
|
ref ctrl_word_t ctrl_data[$],
|
|
input chdr_timestamp_t ctrl_timestamp = 0);
|
|
extern function void read_ctrl (output chdr_header_t header,
|
|
output chdr_ctrl_header_t ctrl_header,
|
|
output ctrl_op_word_t ctrl_op_word,
|
|
output ctrl_word_t ctrl_data[$],
|
|
output chdr_timestamp_t ctrl_timestamp);
|
|
|
|
|
|
// Helper methods
|
|
extern function int header_bytes();
|
|
extern function int mdata_bytes();
|
|
extern function int data_bytes();
|
|
extern function void update_lengths();
|
|
|
|
extern function string sprint_raw();
|
|
extern function string sprint_pretty();
|
|
|
|
extern function bit chdr_word_queues_equal(ref chdr_word_t a[$], ref chdr_word_t b[$]);
|
|
|
|
endclass : ChdrPacket;
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// CHDR BFM Class
|
|
//---------------------------------------------------------------------------
|
|
|
|
class ChdrBfm #(
|
|
parameter int CHDR_W = 64,
|
|
parameter int USER_WIDTH = 1
|
|
) extends AxiStreamBfm #(CHDR_W, USER_WIDTH);
|
|
|
|
typedef ChdrPacket #(CHDR_W) ChdrPacket_t;
|
|
typedef ChdrData #(CHDR_W)::chdr_word_t chdr_word_t;
|
|
|
|
const int BYTES_PER_CHDR_W = CHDR_W / 8;
|
|
|
|
// Default fields used by high-level transaction methods
|
|
chdr_epid_t dst_epid;
|
|
chdr_seq_num_t seq_num;
|
|
|
|
|
|
extern function new (
|
|
virtual AxiStreamIf #(CHDR_W, USER_WIDTH).master master,
|
|
virtual AxiStreamIf #(CHDR_W, USER_WIDTH).slave slave
|
|
);
|
|
|
|
|
|
// Send Transactions
|
|
extern task put_chdr(ChdrPacket_t chdr_packet);
|
|
extern function bit try_put_chdr(ChdrPacket_t chdr_packet);
|
|
|
|
|
|
// Receive Transactions
|
|
extern task get_chdr(output ChdrPacket_t chdr_packet);
|
|
extern function bit try_get_chdr(output ChdrPacket_t chdr_packet);
|
|
extern task peek_chdr(output ChdrPacket_t chdr_packet);
|
|
extern function bit try_peek_chdr(output ChdrPacket_t chdr_packet);
|
|
|
|
|
|
// AXI-Stream/CHDR Conversion Functions
|
|
extern function ChdrPacket_t axis_to_chdr (AxisPacket_t axis_packet);
|
|
extern function AxisPacket_t chdr_to_axis (ChdrPacket_t chdr_packet);
|
|
|
|
endclass : ChdrBfm
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// CHDR Packet Class Methods
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Create a copy of this packet and return a handle to the copy
|
|
function ChdrPacket::ChdrPacket_t ChdrPacket::copy();
|
|
ChdrPacket_t temp;
|
|
temp = new();
|
|
temp.header = this.header;
|
|
temp.timestamp = this.timestamp;
|
|
temp.metadata = this.metadata;
|
|
temp.data = this.data;
|
|
return temp;
|
|
endfunction
|
|
|
|
|
|
// Return true if this packet equals that of the argument
|
|
function bit ChdrPacket::equal(ChdrPacket_t packet);
|
|
if (header != packet.header) return 0;
|
|
if (!chdr_word_queues_equal(data, packet.data)) return 0;
|
|
if (!chdr_word_queues_equal(metadata, packet.metadata)) return 0;
|
|
if (header.pkt_type == CHDR_DATA_WITH_TS && timestamp !== packet.timestamp) return 0;
|
|
return 1;
|
|
endfunction : equal
|
|
|
|
|
|
// Format the contents of the packet into a string (don't dissect contents)
|
|
function string ChdrPacket::sprint_raw();
|
|
string str;
|
|
str = {str, $sformatf("ChdrPacket:\n")};
|
|
str = {str, $sformatf("- header: %p\n", header) };
|
|
str = {str, $sformatf("- timestamp: %X\n", timestamp) };
|
|
str = {str, $sformatf("- metadata:\n") };
|
|
foreach (metadata[i]) begin
|
|
str = {str, $sformatf("%5d> %X\n", i, metadata[i]) };
|
|
end
|
|
str = {str, $sformatf("- data:\n") };
|
|
foreach (data[i]) begin
|
|
str = {str, $sformatf("%5d> %X\n", i, data[i]) };
|
|
end
|
|
return str;
|
|
endfunction : sprint_raw
|
|
|
|
// Format the contents of the packet into a string (dissect contents)
|
|
function string ChdrPacket::sprint_pretty();
|
|
string str;
|
|
str = {str, $sformatf("ChdrPacket:\n")};
|
|
str = {str, $sformatf("- header: %p\n", header) };
|
|
if (header.pkt_type == CHDR_DATA_WITH_TS) begin
|
|
str = {str, $sformatf("- timestamp: %0d\n", timestamp) };
|
|
end
|
|
if (header.num_mdata != '0) begin
|
|
str = {str, $sformatf("- metadata:\n") };
|
|
foreach (metadata[i]) begin
|
|
str = {str, $sformatf("%5d> %X\n", i, metadata[i]) };
|
|
end
|
|
end
|
|
str = {str, $sformatf("- data (%s):\n", header.pkt_type.name) };
|
|
if (header.pkt_type == CHDR_MANAGEMENT) begin
|
|
chdr_header_t tmp_hdr;
|
|
chdr_mgmt_t tmp_mgmt;
|
|
read_mgmt(tmp_hdr, tmp_mgmt);
|
|
str = {str, $sformatf(" > chdr_mgmt_header_t : %p\n", tmp_mgmt.header)};
|
|
foreach (tmp_mgmt.ops[i]) begin
|
|
str = {str, $sformatf(" > %3d: chdr_mgmt_op_t: '{op_payload:0x%12x,op_code:%s,ops_pending:%0d}\n",
|
|
i, tmp_mgmt.ops[i].op_payload, tmp_mgmt.ops[i].op_code.name, tmp_mgmt.ops[i].ops_pending)};
|
|
end
|
|
end else if (header.pkt_type == CHDR_STRM_STATUS) begin
|
|
chdr_header_t tmp_hdr;
|
|
chdr_str_status_t tmp_sts;
|
|
read_stream_status(tmp_hdr, tmp_sts);
|
|
str = {str, $sformatf(" > chdr_str_status_t0 : '{status_info:0x%012x,buff_info:0x%04x,xfer_count_bytes:%0d,xfer_count_pkts:%0d...\n",
|
|
tmp_sts.status_info,tmp_sts.buff_info,tmp_sts.xfer_count_bytes,tmp_sts.xfer_count_pkts)};
|
|
str = {str, $sformatf(" > chdr_str_status_t1 : capacity_pkts:%0d,capacity_bytes:%0d,status:%s,src_epid:%0d}\n",
|
|
tmp_sts.capacity_pkts,tmp_sts.capacity_bytes,tmp_sts.status.name,tmp_sts.src_epid)};
|
|
end else if (header.pkt_type == CHDR_STRM_CMD) begin
|
|
chdr_header_t tmp_hdr;
|
|
chdr_str_command_t tmp_cmd;
|
|
read_stream_cmd(tmp_hdr, tmp_cmd);
|
|
str = {str, $sformatf(" > chdr_str_command_t : %p\n", tmp_cmd)};
|
|
end else if (header.pkt_type == CHDR_CONTROL) begin
|
|
chdr_header_t tmp_hdr;
|
|
chdr_timestamp_t tmp_ts;
|
|
chdr_ctrl_header_t tmp_ctrl_hdr;
|
|
ctrl_op_word_t tmp_op_word;
|
|
ctrl_word_t tmp_ctrl_data[$];
|
|
read_ctrl(tmp_hdr, tmp_ctrl_hdr, tmp_op_word, tmp_ctrl_data, tmp_ts);
|
|
str = {str, $sformatf(" > chdr_ctrl_header_t : %p\n", tmp_ctrl_hdr)};
|
|
if (tmp_ctrl_hdr.has_time)
|
|
str = {str, $sformatf(" > timestamp : %0d\n", tmp_ts)};
|
|
str = {str, $sformatf(" > ctrl_op_word_t : '{status:%s,op_code:%s,byte_enable:0b%4b,address:0x%05x}\n",
|
|
tmp_op_word.status.name, tmp_op_word.op_code.name, tmp_op_word.byte_enable, tmp_op_word.address)};
|
|
foreach (tmp_ctrl_data[i]) begin
|
|
str = {str, $sformatf(" > data %2d : 0x%08x\n", i, tmp_ctrl_data[i])};
|
|
end
|
|
end else begin
|
|
foreach (data[i]) begin
|
|
str = {str, $sformatf("%5d> %X\n", i, data[i]) };
|
|
end
|
|
end
|
|
return str;
|
|
endfunction : sprint_pretty
|
|
|
|
function string ChdrPacket::sprint(bit pretty = 1);
|
|
if (pretty)
|
|
return sprint_pretty();
|
|
else
|
|
return sprint_raw();
|
|
endfunction: sprint
|
|
|
|
// Print the contents of the packet
|
|
function void ChdrPacket::print(bit pretty = 1);
|
|
$display(sprint(pretty));
|
|
endfunction : print
|
|
|
|
|
|
// Populate the packet with the provided info. The packet Length and NumMData
|
|
// fields are calculated and set in this method. Omitting the
|
|
// data_byte_length argument, or providing a negative value, causes this
|
|
// method to calculate the payload length based on the size of the data
|
|
// array.
|
|
function void ChdrPacket::write_raw (
|
|
ref chdr_header_t header,
|
|
ref chdr_word_t data[$],
|
|
input chdr_word_t metadata[$] = {},
|
|
input chdr_timestamp_t timestamp = 0,
|
|
input int data_byte_length = -1
|
|
);
|
|
this.header = header;
|
|
this.timestamp = timestamp;
|
|
this.data = data;
|
|
this.metadata = metadata;
|
|
update_lengths();
|
|
|
|
// Adjust length field according to data_byte_length
|
|
if (data_byte_length >= 0) begin
|
|
int array_num_bytes;
|
|
|
|
// Make sure number of words for data_byte_length matches data length
|
|
assert((data_byte_length+(BYTES_PER_CHDR_W-1)) / BYTES_PER_CHDR_W == data.size()) else begin
|
|
$error("ChdrPacket::write_raw: data_byte_length doesn't correspond to number of words in data");
|
|
end
|
|
|
|
array_num_bytes = data.size() * BYTES_PER_CHDR_W;
|
|
this.header.length -= (array_num_bytes - data_byte_length);
|
|
end
|
|
endfunction : write_raw
|
|
|
|
|
|
// Read the contents of this packet
|
|
function void ChdrPacket::read_raw (
|
|
output chdr_header_t header,
|
|
output chdr_word_t data[$],
|
|
output chdr_word_t metadata[$],
|
|
output chdr_timestamp_t timestamp,
|
|
output int data_byte_length
|
|
);
|
|
header = this.header;
|
|
data = this.data;
|
|
metadata = this.metadata;
|
|
timestamp = this.timestamp;
|
|
data_byte_length = data_bytes();
|
|
endfunction : read_raw
|
|
|
|
|
|
// Populate this packet as a status packet
|
|
function void ChdrPacket::write_stream_status (
|
|
ref chdr_header_t header,
|
|
ref chdr_str_status_t status
|
|
);
|
|
header.pkt_type = CHDR_STRM_STATUS; // Update packet type in header
|
|
this.header = header;
|
|
data = {};
|
|
for (int i = 0; i < $bits(status); i += CHDR_W) begin
|
|
data.push_back( status[i +: CHDR_W] );
|
|
end
|
|
update_lengths();
|
|
endfunction : write_stream_status
|
|
|
|
|
|
// Read this packet as a status packet
|
|
function void ChdrPacket::read_stream_status (
|
|
output chdr_header_t header,
|
|
output chdr_str_status_t status
|
|
);
|
|
// Make sure it's a stream status packet
|
|
assert(this.header.pkt_type == CHDR_STRM_STATUS) else begin
|
|
$error("ChdrPacket::read_stream_status: Packet type is not CHDR_STRM_STATUS");
|
|
end
|
|
|
|
// Make sure we have enough payload
|
|
assert($bits(status) <= $bits(data)) else begin
|
|
$error("ChdrPacket::read_stream_status: Not enough data for status payload");
|
|
end
|
|
|
|
header = this.header;
|
|
for (int i = 0; i < $bits(status)/CHDR_W; i++) begin
|
|
status[i*CHDR_W +: CHDR_W] = data[i];
|
|
end
|
|
endfunction : read_stream_status
|
|
|
|
|
|
// Populate this packet as a command packet
|
|
function void ChdrPacket::write_stream_cmd (
|
|
ref chdr_header_t header,
|
|
ref chdr_str_command_t command
|
|
);
|
|
header.pkt_type = CHDR_STRM_CMD; // Update packet type in header
|
|
this.header = header;
|
|
data = {};
|
|
for (int i = 0; i < $bits(command); i += CHDR_W) begin
|
|
data.push_back( command[i +: CHDR_W] );
|
|
end
|
|
update_lengths();
|
|
endfunction : write_stream_cmd
|
|
|
|
|
|
// Read this packet as a command packet
|
|
function void ChdrPacket::read_stream_cmd (
|
|
output chdr_header_t header,
|
|
output chdr_str_command_t command
|
|
);
|
|
// Make sure it's a stream command packet
|
|
assert(this.header.pkt_type == CHDR_STRM_CMD) else begin
|
|
$error("ChdrPacket::read_stream_cmd: Packet type is not CHDR_STRM_CMD");
|
|
end
|
|
|
|
// Make sure we have enough payload
|
|
assert($bits(command) <= $bits(data)) else begin
|
|
$error("ChdrPacket::read_stream_cmd: Not enough data for command payload");
|
|
end
|
|
|
|
header = this.header;
|
|
for (int i = 0; i < $bits(command)/CHDR_W; i++) begin
|
|
command[i*CHDR_W +: CHDR_W] = data[i];
|
|
end
|
|
endfunction : read_stream_cmd
|
|
|
|
|
|
// Populate this packet as a management packet
|
|
function void ChdrPacket::write_mgmt (
|
|
ref chdr_header_t header,
|
|
ref chdr_mgmt_t mgmt
|
|
);
|
|
header.pkt_type = CHDR_MANAGEMENT; // Update packet type in header
|
|
this.header = header;
|
|
data = {};
|
|
|
|
// Insert the header
|
|
data.push_back( mgmt.header );
|
|
|
|
// Insert the ops
|
|
foreach (mgmt.ops[i]) begin
|
|
data.push_back( mgmt.ops[i] );
|
|
end
|
|
|
|
update_lengths();
|
|
endfunction : write_mgmt
|
|
|
|
|
|
// Read this packet as a management packet
|
|
function void ChdrPacket::read_mgmt (
|
|
output chdr_header_t header,
|
|
output chdr_mgmt_t mgmt
|
|
);
|
|
int num_ops;
|
|
|
|
// Make sure it's a management packet
|
|
assert(header.pkt_type == CHDR_MANAGEMENT) else begin
|
|
$error("ChdrPacket::read_mgmt: Packet type is not CHDR_MANAGEMENT");
|
|
end
|
|
|
|
header = this.header;
|
|
|
|
num_ops = data_bytes()/BYTES_PER_CHDR_W - 1; // Num words, minus one for the header
|
|
|
|
// Make sure we have enough payload
|
|
assert(1 + num_ops <= data.size()) else begin
|
|
$error("ChdrPacket::read_mgmt: Not enough data for management payload");
|
|
end
|
|
|
|
// Read the management header
|
|
mgmt.header = data[0];
|
|
|
|
// Read the management operations
|
|
for (int i = 0; i < num_ops; i++) begin
|
|
mgmt.ops.push_back(data[i+1]);
|
|
end
|
|
endfunction : read_mgmt
|
|
|
|
|
|
// Populate this packet as a control packet
|
|
function void ChdrPacket::write_ctrl (
|
|
ref chdr_header_t header,
|
|
ref chdr_ctrl_header_t ctrl_header,
|
|
ref ctrl_op_word_t ctrl_op_word,
|
|
ref ctrl_word_t ctrl_data[$],
|
|
input chdr_timestamp_t ctrl_timestamp = 0
|
|
);
|
|
bit partial_word;
|
|
ctrl_word_t mandatory_data;
|
|
ChdrData #(CHDR_W, 64)::item_queue_t data64;
|
|
|
|
header.pkt_type = CHDR_CONTROL; // Update packet type in header
|
|
this.header = header;
|
|
data64 = {};
|
|
|
|
// Insert word 0 of control payload
|
|
data64.push_back(ctrl_header);
|
|
|
|
// Insert word 1 of control payload, if timestamp is used
|
|
if (ctrl_header.has_time) begin
|
|
data64.push_back(ctrl_timestamp);
|
|
end
|
|
|
|
// Make sure the amount of data passed matches the header
|
|
assert (ctrl_header.num_data == ctrl_data.size()) else begin
|
|
$error("ChdrPacket::write_ctrl: NumData doesn't match ctrl_data[] size");
|
|
end
|
|
|
|
// Insert word 2 of control payload, the operation word
|
|
// and first word of control data.
|
|
mandatory_data = (ctrl_header.num_data > 0) ? ctrl_data[0] : '0;
|
|
data64.push_back({mandatory_data, ctrl_op_word[31:0]});
|
|
// We have a half CHDR word if num_data is even
|
|
partial_word = (ctrl_header.num_data[0] == '0);
|
|
|
|
// Insert remaining data, if present
|
|
for (int i = 1; i < ctrl_data.size(); i+=2) begin
|
|
if (i == ctrl_data.size()-1) begin
|
|
// num_data must be even in this case, so last word is half filled
|
|
data64.push_back({ 32'b0, ctrl_data[i] });
|
|
end else begin
|
|
data64.push_back({ ctrl_data[i+1], ctrl_data[i] });
|
|
end
|
|
end
|
|
|
|
// Convert from 64-bit words to CHDR_W-bit words
|
|
data = ChdrData #(CHDR_W, 64)::item_to_chdr(data64);
|
|
|
|
update_lengths();
|
|
endfunction : write_ctrl
|
|
|
|
|
|
// Read this packet as a control packet
|
|
function void ChdrPacket::read_ctrl (
|
|
output chdr_header_t header,
|
|
output chdr_ctrl_header_t ctrl_header,
|
|
output ctrl_op_word_t ctrl_op_word,
|
|
output ctrl_word_t ctrl_data[$],
|
|
output chdr_timestamp_t ctrl_timestamp
|
|
);
|
|
ChdrData #(CHDR_W, 32)::item_queue_t data32;
|
|
chdr_word_t chdr_op_word;
|
|
int ctrl_packet_size;
|
|
int dptr = 0;
|
|
|
|
// Make sure it's a stream status packet
|
|
assert(this.header.pkt_type == CHDR_CONTROL) else begin
|
|
$error("ChdrPacket::read_ctrl: Packet type is not CHDR_CONTROL");
|
|
end
|
|
|
|
// Convert packet to 32-bit words for easier parsing
|
|
data32 = ChdrData #(CHDR_W, 32)::chdr_to_item(data, data_bytes());
|
|
|
|
// CHDR header
|
|
header = this.header;
|
|
|
|
// Ctrl header
|
|
ctrl_header = { data32[dptr+1], data32[dptr] };
|
|
dptr += 2;
|
|
|
|
// Make sure we have enough payload. Calculate expected size in 32-bit
|
|
// words.
|
|
ctrl_packet_size = 3 + ctrl_header.num_data; // header + op_word + data
|
|
if (ctrl_header.has_time) ctrl_packet_size += 2; // timestamp
|
|
assert(data32.size() < ctrl_packet_size) else begin
|
|
$error("ChdrPacket::read_ctrl: Not enough CHDR payload for control packet");
|
|
end
|
|
assert(data32.size() > ctrl_packet_size) else begin
|
|
$warning("ChdrPacket::read_ctrl: Excess CHDR payload for control packet");
|
|
end
|
|
|
|
// Timestamp (optional)
|
|
if(ctrl_header.has_time) begin
|
|
ctrl_timestamp = { data32[dptr+1], data32[dptr] };
|
|
dptr += 2;
|
|
end
|
|
|
|
// Operation word
|
|
ctrl_op_word = data32[dptr++];
|
|
|
|
// Data words
|
|
ctrl_data = {};
|
|
while (dptr < data32.size()) begin
|
|
ctrl_data.push_back(data32[dptr++]);
|
|
end
|
|
|
|
endfunction : read_ctrl
|
|
|
|
|
|
// Calculate the header size (including timestamp), in bytes, from the header
|
|
// information.
|
|
function int ChdrPacket::header_bytes();
|
|
if (CHDR_W == 64 && header.pkt_type == CHDR_DATA_WITH_TS) begin
|
|
header_bytes = 2 * BYTES_PER_CHDR_W; // Two words (header + timestamp)
|
|
end else begin
|
|
header_bytes = BYTES_PER_CHDR_W; // One word, regardless of timestamp
|
|
end
|
|
endfunction : header_bytes
|
|
|
|
|
|
// Calculate the metadata size from the header information.
|
|
function int ChdrPacket::mdata_bytes();
|
|
mdata_bytes = header.num_mdata * BYTES_PER_CHDR_W;
|
|
endfunction : mdata_bytes
|
|
|
|
|
|
// Calculate the data payload size, in bytes, from the header information
|
|
function int ChdrPacket::data_bytes();
|
|
data_bytes = header.length - header_bytes() - mdata_bytes();
|
|
endfunction : data_bytes;
|
|
|
|
|
|
// Update the length and num_mdata header fields of the packet based on the
|
|
// size of the metadata queue and the data queue.
|
|
function void ChdrPacket::update_lengths();
|
|
int num_bytes;
|
|
int num_mdata;
|
|
|
|
// Calculate NumMData based on the size of metadata queue
|
|
num_mdata = metadata.size();
|
|
assert(num_mdata < 2**$bits(chdr_num_mdata_t)) else
|
|
$fatal(1, "ChdrPacket::update_lengths(): Calculated NumMData exceeds maximum size");
|
|
|
|
// Calculate the Length field
|
|
num_bytes = header_bytes() + // Header
|
|
num_mdata * BYTES_PER_CHDR_W + // Metadata
|
|
data.size() * BYTES_PER_CHDR_W; // Payload
|
|
assert(num_bytes < 2**$bits(chdr_length_t)) else
|
|
$fatal(1, "ChdrPacket::update_lengths(): Calculated Length exceeds maximum size");
|
|
|
|
// Update header
|
|
header.num_mdata = num_mdata;
|
|
header.length = num_bytes;
|
|
endfunction : update_lengths
|
|
|
|
|
|
// Returns 1 if the queues have the same contents, otherwise returns 0. This
|
|
// function is equivalent to (a == b), but this doesn't work correctly yet in
|
|
// Vivado 2018.3.
|
|
function automatic bit ChdrPacket::chdr_word_queues_equal(ref chdr_word_t a[$], ref chdr_word_t b[$]);
|
|
chdr_word_t x, y;
|
|
if (a.size() != b.size()) return 0;
|
|
foreach (a[i]) begin
|
|
x = a[i];
|
|
y = b[i];
|
|
if (x !== y) return 0;
|
|
end
|
|
return 1;
|
|
endfunction : chdr_word_queues_equal
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// CHDR BFM Class Methods
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
// Class constructor. This must be given an interface for the master
|
|
// connection and an interface for the slave connection.
|
|
function ChdrBfm::new (
|
|
virtual AxiStreamIf #(CHDR_W, USER_WIDTH).master master,
|
|
virtual AxiStreamIf #(CHDR_W, USER_WIDTH).slave slave
|
|
);
|
|
super.new(master, slave);
|
|
assert(CHDR_W % 64 == 0) else begin
|
|
$fatal(1, "ChdrBfm::new: CHDR bus width must be a multiple of 64 bits");
|
|
end
|
|
endfunction : new
|
|
|
|
|
|
// Queue the provided packet for transmission
|
|
task ChdrBfm::put_chdr (ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
|
|
axis_packet = chdr_to_axis(chdr_packet);
|
|
super.put(axis_packet);
|
|
endtask : put_chdr
|
|
|
|
|
|
// Attempt to queue the provided packet for transmission. Return 1 if
|
|
// successful, return 0 if the queue is full.
|
|
function bit ChdrBfm::try_put_chdr (ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
bit status;
|
|
|
|
axis_packet = chdr_to_axis(chdr_packet);
|
|
return super.try_put(axis_packet);
|
|
endfunction : try_put_chdr
|
|
|
|
|
|
// Get the next packet when it becomes available (wait if necessary)
|
|
task ChdrBfm::get_chdr (output ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
super.get(axis_packet);
|
|
chdr_packet = axis_to_chdr(axis_packet);
|
|
endtask : get_chdr
|
|
|
|
|
|
// Get the next packet if there's one available and return 1. Return 0 if
|
|
// there's no packet available.
|
|
function bit ChdrBfm::try_get_chdr (output ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
if (!super.try_get(axis_packet)) return 0;
|
|
chdr_packet = axis_to_chdr(axis_packet);
|
|
return 1;
|
|
endfunction : try_get_chdr
|
|
|
|
|
|
// Get the next packet when it becomes available (wait if necessary), but
|
|
// don't remove it from the receive queue.
|
|
task ChdrBfm::peek_chdr (output ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
super.peek(axis_packet);
|
|
chdr_packet = axis_to_chdr(axis_packet);
|
|
endtask : peek_chdr
|
|
|
|
|
|
// Get the next packet if there's one available and return 1, but don't
|
|
// remove it from the receive queue. Return 0 if there's no packet available.
|
|
function bit ChdrBfm::try_peek_chdr (output ChdrPacket_t chdr_packet);
|
|
AxisPacket_t axis_packet;
|
|
if (!super.try_get(axis_packet)) return 0;
|
|
chdr_packet = axis_to_chdr(axis_packet);
|
|
return 1;
|
|
endfunction : try_peek_chdr
|
|
|
|
|
|
// Convert the data payload of an AXI Stream packet data structure to a CHDR
|
|
// packet data structure.
|
|
function ChdrBfm::ChdrPacket_t ChdrBfm::axis_to_chdr (AxisPacket_t axis_packet);
|
|
enum int { ST_HEADER, ST_TIMESTAMP, ST_METADATA, ST_PAYLOAD } rx_state;
|
|
data_t word;
|
|
int num_rx_mdata;
|
|
int num_rx_bytes;
|
|
ChdrPacket_t chdr_packet = new();
|
|
|
|
rx_state = ST_HEADER;
|
|
|
|
for(int i = 0; i < axis_packet.data.size(); i++) begin
|
|
word = axis_packet.data[i];
|
|
|
|
case (rx_state)
|
|
ST_HEADER : begin
|
|
num_rx_bytes += BYTES_PER_CHDR_W;
|
|
chdr_packet.header = word[63:0];
|
|
|
|
// Depending on the size of the word, we could have just the header
|
|
// or both the header and the timestamp in this word.
|
|
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin
|
|
if (CHDR_W >= 128) begin
|
|
chdr_packet.timestamp = word[127:64];
|
|
rx_state = ST_METADATA;
|
|
end else begin
|
|
rx_state = ST_TIMESTAMP;
|
|
end
|
|
end else begin
|
|
rx_state = ST_METADATA;
|
|
end
|
|
|
|
// Check if there's no metadata, in which case we can skip it
|
|
if (rx_state == ST_METADATA && chdr_packet.header.num_mdata == 0) begin
|
|
rx_state = ST_PAYLOAD;
|
|
end
|
|
end
|
|
ST_TIMESTAMP : begin
|
|
num_rx_bytes += BYTES_PER_CHDR_W;
|
|
chdr_packet.timestamp = word;
|
|
rx_state = (chdr_packet.header.num_mdata > 0) ? ST_METADATA : ST_PAYLOAD;
|
|
end
|
|
ST_METADATA : begin
|
|
chdr_packet.metadata.push_back(word);
|
|
num_rx_mdata++;
|
|
num_rx_bytes += BYTES_PER_CHDR_W;
|
|
if (num_rx_mdata == chdr_packet.header.num_mdata) rx_state = ST_PAYLOAD;
|
|
end
|
|
ST_PAYLOAD : begin
|
|
chdr_packet.data.push_back(word);
|
|
num_rx_bytes += BYTES_PER_CHDR_W;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
assert (rx_state == ST_PAYLOAD) else begin
|
|
$error("ChdrBfm::axis_to_chdr: Malformed CHDR packet");
|
|
end
|
|
|
|
// Check length field, noting that the last word may be partially filled
|
|
assert (chdr_packet.header.length >= num_rx_bytes-(BYTES_PER_CHDR_W-1) &&
|
|
chdr_packet.header.length <= num_rx_bytes) else begin
|
|
$error("ChdrBfm::axis_to_chdr: Incorrect CHDR length");
|
|
end
|
|
|
|
return chdr_packet;
|
|
|
|
endfunction : axis_to_chdr
|
|
|
|
|
|
// Convert a CHDR packet data structure to a an AXI-Stream packet data
|
|
// structure.
|
|
function ChdrBfm::AxisPacket_t ChdrBfm::chdr_to_axis (ChdrPacket_t chdr_packet);
|
|
int num_words, expected_words;
|
|
data_t bus_word = 0;
|
|
AxisPacket_t axis_packet = new();
|
|
|
|
// Check that we have the right number of metadata words
|
|
assert (chdr_packet.metadata.size() == chdr_packet.header.num_mdata) else begin
|
|
$error("ChdrBfm::chdr_to_axis: Packet metadata size doesn't match header NumMData field");
|
|
end
|
|
|
|
// Calculate the number of words needed to represent this packet
|
|
num_words = chdr_packet.data.size() + chdr_packet.metadata.size();
|
|
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS && CHDR_W == 64) begin
|
|
// Add two words, one for header and one for timestamp
|
|
num_words += 2;
|
|
end else begin
|
|
// Add one word only for header (which may or may not include a timestamp)
|
|
num_words += 1;
|
|
end
|
|
|
|
// Calculate the number of words represented by the Length field
|
|
expected_words = chdr_packet.header.length / BYTES_PER_CHDR_W;
|
|
if (chdr_packet.header.length % BYTES_PER_CHDR_W != 0) expected_words++;
|
|
|
|
// Make sure length field matches actual packet length
|
|
assert (num_words == expected_words) else begin
|
|
$error("ChdrBfm::chdr_to_axis: Packet size doesn't match header Length field");
|
|
end
|
|
|
|
// Insert header
|
|
bus_word[63:0] = chdr_packet.header;
|
|
if (CHDR_W == 64) begin
|
|
axis_packet.data.push_back(bus_word);
|
|
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin
|
|
// Insert timestamp
|
|
axis_packet.data.push_back(chdr_packet.timestamp);
|
|
end
|
|
end else begin
|
|
// Copy the timestamp word from the header, regardless of whether or not
|
|
// this packet uses the timestamp field.
|
|
bus_word[127:64] = chdr_packet.timestamp;
|
|
axis_packet.data.push_back(bus_word);
|
|
end
|
|
|
|
// Insert metadata
|
|
while (chdr_packet.metadata.size() > 0) begin
|
|
bus_word = chdr_packet.metadata.pop_front();
|
|
axis_packet.data.push_back(bus_word);
|
|
end
|
|
|
|
// Insert payload
|
|
while (chdr_packet.data.size() > 0) begin
|
|
bus_word = chdr_packet.data.pop_front();
|
|
axis_packet.data.push_back(bus_word);
|
|
end
|
|
|
|
return axis_packet;
|
|
|
|
endfunction : chdr_to_axis
|
|
|
|
|
|
endpackage : PkgChdrBfm
|