// // Copyright 2020 Ettus Research, A National Instruments Company // // SPDX-License-Identifier: LGPL-3.0-or-later // // Module: PkgChdrIfaceBfm // // Description: This package includes a high-level bus functional model (BFM) // for the AXIS-CHDR interface of a Transport Adapter or Stream Endpoint. // package PkgChdrIfaceBfm; import PkgChdrUtils::*; import PkgChdrBfm::*; typedef struct packed { chdr_vc_t vc; chdr_eob_t eob; chdr_eov_t eov; bit has_time; chdr_timestamp_t timestamp; } packet_info_t; // Return 1 if the packet info is equivalent, 0 otherwise. function automatic bit packet_info_equal(const ref packet_info_t a, b); // If there's no time then the timestamp value doesn't matter, so make them // the same for comparison. if (!a.has_time) begin packet_info_t a_copy = a; a_copy.timestamp = b.timestamp; return a_copy == b; end return a == b; endfunction : packet_info_equal class ChdrIfaceBfm #(CHDR_W = 64, ITEM_W = 32) extends ChdrBfm #(CHDR_W); // Redefine the ChdrPacket_t and chdr_word_t data types from ChdrBfm due to // a bug in Vivado 2019.1. typedef ChdrPacket #(CHDR_W) ChdrPacket_t; typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t; typedef ChdrData #(CHDR_W, ITEM_W)::item_t item_t; localparam int BYTES_PER_CHDR_W = CHDR_W/8; localparam int BYTES_PER_ITEM_W = ITEM_W/8; chdr_seq_num_t seq_num; // Sequence number protected int max_payload_length; // Maximum number of payload bytes per packet protected int ticks_per_word; // Timestamp increment per CHDR_W sized word // Class constructor to create a new BFM instance. // // m_chdr: Interface for the master connection (BFM's CHDR output) // s_chdr: Interface for the slave connection (BFM's CHDR input) // function new( virtual AxiStreamIf #(CHDR_W).master m_chdr, virtual AxiStreamIf #(CHDR_W).slave s_chdr, input int max_payload_length = 2**$bits(chdr_length_t), input int ticks_per_word = CHDR_W/ITEM_W ); super.new(m_chdr, s_chdr); this.seq_num = 0; assert (CHDR_W % ITEM_W == 0) else begin $fatal(1, "ChdrIfaceBfm::new: CHDR_W must be a multiple of ITEM_W"); end set_max_payload_length(max_payload_length); set_ticks_per_word(ticks_per_word); endfunction : new // Set the maximum payload size for packets. This value is used to split // large send requests across multiple packets. // // max_length: Maximum payload length in bytes for each packet // function void set_max_payload_length(int max_payload_length); assert (max_payload_length % BYTES_PER_CHDR_W == 0) else begin $fatal(1, "ChdrIfaceBfm::set_max_payload_length: max_payload_length must be a multiple of CHDR_W in bytes"); end this.max_payload_length = max_payload_length; endfunction // Return the maximum payload size for packets. This value is used to split // large send requests across multiple packets. function int get_max_payload_length(); return max_payload_length; endfunction // Set the timestamp ticks per CHDR_W sized word. // // ticks_per_word: Amount to increment the timestamp per CHDR_W sized word // function void set_ticks_per_word(int ticks_per_word); this.ticks_per_word = ticks_per_word; endfunction // Return the timestamp ticks per CHDR_W sized word. function int get_ticks_per_word(); return ticks_per_word; endfunction // Send a CHDR data packet. // // data: Data words to insert into the CHDR packet. // data_bytes: The number of data bytes in the CHDR packet. This // is useful if the data is not a multiple of the // chdr_word_t size. // metadata: Metadata words to insert into the CHDR packet. Omit this // argument (or set to an empty array) to not include // metadata. // pkt_info: Data structure containing packet header information. // task send ( input chdr_word_t data[$], input int data_bytes = -1, input chdr_word_t metadata[$] = {}, input packet_info_t pkt_info = 0 ); ChdrPacket_t chdr_packet; chdr_header_t chdr_header; // Build packet chdr_packet = new(); chdr_header = '{ vc : pkt_info.vc, eob : pkt_info.eob, eov : pkt_info.eov, seq_num : seq_num++, pkt_type : pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS, dst_epid : dst_epid, default : 0 }; chdr_packet.write_raw(chdr_header, data, metadata, pkt_info.timestamp, data_bytes); // Send the packet put_chdr(chdr_packet); endtask : send // Send a CHDR data packet, filling the payload with items. // // items: Data items to insert into the CHDR packet. // metadata: Metadata words to insert into the CHDR packet. Omit this // argument (or set to an empty array) to not include metadata. // pkt_info: Data structure containing packet header information. // task send_items ( input item_t items[$], input chdr_word_t metadata[$] = {}, input packet_info_t pkt_info = 0 ); chdr_word_t data[$]; data = ChdrData#(CHDR_W, ITEM_W)::item_to_chdr(items); send(data, items.size()*BYTES_PER_ITEM_W, metadata, pkt_info); endtask : send_items // Send data as one or more CHDR data packets. The input data and metadata // is automatically broken into max_payload_length'd packets. The // timestamp, if present, is set for the first packet and updated for // subsequent packets. The EOB and EOV are only applied to the last packet. // // data: Data words to insert into the CHDR packet. // data_bytes: The number of data bytes in the CHDR packet. This // is useful if the data is not a multiple of the // chdr_word_t size. // metadata: Metadata words to insert into the CHDR packet. Omit this // argument (or set to an empty array) to not include // metadata. // pkt_info: Data structure containing packet header information. // task send_packets ( input chdr_word_t data[$], input int data_bytes = -1, input chdr_word_t metadata[$] = {}, input packet_info_t pkt_info = 0 ); ChdrPacket_t chdr_packet; chdr_header_t chdr_header; chdr_pkt_type_t pkt_type; chdr_word_t timestamp; int num_pkts; int payload_length; int first_dword, last_dword; int first_mword, last_mword; bit eob, eov; chdr_word_t temp_data[$]; chdr_word_t temp_mdata[$]; num_pkts = $ceil(real'(data.size()*BYTES_PER_CHDR_W) / max_payload_length); pkt_type = pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS; timestamp = pkt_info.timestamp; if (data_bytes < 0) data_bytes = data.size() * BYTES_PER_CHDR_W; // Make sure there's not too much metadata for this number of packets assert(metadata.size() <= num_pkts * (2**$bits(chdr_num_mdata_t)-1)) else $fatal(1, "ChdrIfaceBfm::send: Too much metadata for this send request"); // Send the data, one packet at a time. for (int i = 0; i < num_pkts; i++) begin chdr_packet = new(); // Figure out which data chunk to send next if (i == num_pkts-1) begin // The last packet, which may or may not be full-sized eob = pkt_info.eob; eov = pkt_info.eov; payload_length = data_bytes - (num_pkts-1) * max_payload_length; first_dword = i*max_payload_length/BYTES_PER_CHDR_W; last_dword = data.size()-1; first_mword = i*(2**$bits(chdr_num_mdata_t)-1); last_mword = metadata.size()-1; end else begin // A full-sized packet, not the last eob = 1'b0; eov = 1'b0; payload_length = max_payload_length; first_dword = (i+0)*max_payload_length / BYTES_PER_CHDR_W; last_dword = (i+1)*max_payload_length / BYTES_PER_CHDR_W - 1; first_mword = (i+0)*(2**$bits(chdr_num_mdata_t)-1); last_mword = (i+1)*(2**$bits(chdr_num_mdata_t)-1) - 1; last_mword = last_mword > metadata.size() ? metadata.size() : last_mword; end // Build the packet chdr_header = '{ vc : pkt_info.vc, eob : eob, eov : eov, seq_num : seq_num++, pkt_type : pkt_type, dst_epid : dst_epid, default : 0 }; // Copy region of data and metadata to be sent in next packet temp_data = data[first_dword : last_dword]; if (first_mword < metadata.size()) temp_mdata = metadata[first_mword : last_mword]; else temp_mdata = {}; // Build the packet chdr_packet.write_raw( chdr_header, temp_data, temp_mdata, timestamp, payload_length ); // Send the packet put_chdr(chdr_packet); // Update timestamp for next packet (in case this is not the last) timestamp += max_payload_length/BYTES_PER_CHDR_W * ticks_per_word; end endtask : send_packets // Send one or more CHDR data packets, filling the payload with items. The // input data and metadata is automatically broken into // max_payload_length'd packets. The timestamp, if present, is set for the // first packet and updated for subsequent packets. The EOB and EOV are // only applied to the last packet. // // items: Data items to insert into the payload of the CHDR packets. // metadata: Metadata words to insert into the CHDR packet. Omit this // argument (or set to an empty array) to not include metadata. // pkt_info: Data structure containing packet header information. // task send_packets_items ( input item_t items[$], input chdr_word_t metadata[$] = {}, input packet_info_t pkt_info = 0 ); chdr_word_t data[$]; data = ChdrData#(CHDR_W, ITEM_W)::item_to_chdr(items); send_packets(data, items.size()*BYTES_PER_ITEM_W, metadata, pkt_info); endtask : send_packets_items // Receive a CHDR data packet and extract its contents. // // data: Data words from the received CHDR packet. // data_bytes: The number of data bytes in the CHDR packet. This // is useful if the data is not a multiple of the // chdr_word_t size. // metadata: Metadata words from the received CHDR packet. This // will be an empty array if there was no metadata. // pkt_info: Data structure to receive packet header information. // task recv_adv ( output chdr_word_t data[$], output int data_bytes, output chdr_word_t metadata[$], output packet_info_t pkt_info ); ChdrPacket_t chdr_packet; get_chdr(chdr_packet); data = chdr_packet.data; data_bytes = chdr_packet.data_bytes(); metadata = chdr_packet.metadata; pkt_info.timestamp = chdr_packet.timestamp; pkt_info.vc = chdr_packet.header.vc; pkt_info.eob = chdr_packet.header.eob; pkt_info.eov = chdr_packet.header.eov; pkt_info.has_time = chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS ? 1 : 0; endtask : recv_adv // Receive a CHDR data packet and extract its contents, putting the payload // into a queue of items. // // items: Items extracted from the payload of the received packet. // metadata: Metadata words from the received CHDR packet. This will be // an empty array if there was no metadata. // pkt_info: Data structure to receive packet header information. // task recv_items_adv ( output item_t items[$], output chdr_word_t metadata[$], output packet_info_t pkt_info ); chdr_word_t data[$]; int data_bytes; recv_adv(data, data_bytes, metadata, pkt_info); items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(data, data_bytes); assert (data_bytes % BYTES_PER_ITEM_W == 0) else begin $error({"ChdrIfaceBfm::recv_items_adv: ", "Received data was not a multiple of items"}); end endtask : recv_items_adv // Receive a CHDR data packet and extract the data. Any metadata or // timestamp, if present, are discarded. // // data: Data words from the received CHDR packet. // data_bytes: The number of data bytes in the CHDR packet. This // is useful if the data is not a multiple of the // chdr_word_t size. // task recv(output chdr_word_t data[$], output int data_bytes); ChdrPacket_t chdr_packet; get_chdr(chdr_packet); data = chdr_packet.data; data_bytes = chdr_packet.data_bytes(); endtask : recv // Receive a CHDR data packet and extract its payload into a queue of // items. Any metadata or timestamp, if present, are discarded. // // items: Data items extracted from payload of the received CHDR packet. // task recv_items( output item_t items[$] ); chdr_word_t data[$]; int data_bytes; recv(data, data_bytes); items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(data, data_bytes); assert (data_bytes % BYTES_PER_ITEM_W == 0) else begin $error({"ChdrIfaceBfm::recv_items: ", "Received data was not a multiple of items"}); end endtask : recv_items // Receive one ore more CHDR data packets and extract their contents, // putting the payload into a queue of items. Any metadata or timestamp, if // present, are discarded. // // items: Items extracted from the payload of the received packets. // num_items: (Optional) Minimum number of items to receive. This must be // provided, unless eob or eov are set. Defaults to -1, which // means that the number of items is not limited. // eob: (Optional) Receive up until the next End of Burst (EOB). // Default value is 1, so an entire burst is received. // eov: (Optional) Receive up until the next End of Vector (EOV). // task recv_packets_items( output item_t items[$], input int num_items = -1, // Receive a full burst by default input bit eob = 1, input bit eov = 0 ); chdr_word_t metadata[$]; packet_info_t pkt_info; recv_packets_items_adv(items, metadata, pkt_info, num_items, eob, eov); endtask : recv_packets_items // Receive one or more CHDR data packets and extract their contents, // putting the payload into a queue of items and the metadata into a queue // of CHDR words. // // items: Items extracted from the payload of the received packets. // metadata: Metadata words from the received CHDR packets. This will be // an empty array if there was no metadata. // pkt_info: Data structure to receive packet information. The // timestamp, if present, will correspond to the time of the // first sample, whereas vc/eob/eov will correspond to the // state of the last packet. // num_items: (Optional) Minimum number of items to receive. This must be // provided, unless eob or eov are set. Defaults to -1, which // means that the number of items is not limited. // eob: (Optional) Receive up until the next End of Burst (EOB). // Default value is 1, so an entire burst is received. // eov: (Optional) Receive up until the next End of Vector (EOV). // task recv_packets_items_adv( output item_t items[$], output chdr_word_t metadata[$], output packet_info_t pkt_info, input int num_items = -1, // Receive a full burst by default input bit eob = 1, input bit eov = 0 ); chdr_word_t pkt_data[$]; chdr_word_t pkt_metadata[$]; int pkt_data_bytes; int item_count; packet_info_t time_info; item_t new_items[$]; if (num_items < 0) begin assert (eob || eov) else begin $fatal(1, {"ChdrIfaceBfm::recv_packets_items_adv: ", "eob or eov must be set when num_items is not limited"}); end num_items = 32'h7FFF_FFFF; end time_info = 0; items = {}; metadata = {}; while (items.size() < num_items) begin // Receive the next packet recv_adv(pkt_data, pkt_data_bytes, pkt_metadata, pkt_info); if (items.size() == 0) begin // First packet, so grab the timestamp if it exists if (pkt_info.has_time) time_info = pkt_info; end // Enqueue the data new_items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(pkt_data, pkt_data_bytes); items = {items, new_items}; assert (pkt_data_bytes % BYTES_PER_ITEM_W == 0) else begin $error({"ChdrIfaceBfm::recv_packets_items_adv: ", "Received data was not a multiple of items"}); end // Enqueue the metadata metadata = {metadata, pkt_metadata}; if ((eob && pkt_info.eob) || (eov && pkt_info.eov)) break; end // Restore timestamp from the first packet pkt_info.has_time = time_info.has_time; pkt_info.timestamp = time_info.timestamp; endtask : recv_packets_items_adv endclass : ChdrIfaceBfm endpackage : PkgChdrIfaceBfm