1// 2// Copyright 2020 Ettus Research, A National Instruments Company 3// 4// SPDX-License-Identifier: LGPL-3.0-or-later 5// 6// Module: PkgChdrIfaceBfm 7// 8// Description: This package includes a high-level bus functional model (BFM) 9// for the AXIS-CHDR interface of a Transport Adapter or Stream Endpoint. 10// 11 12package PkgChdrIfaceBfm; 13 14 import PkgChdrUtils::*; 15 import PkgChdrBfm::*; 16 17 18 typedef struct packed { 19 chdr_vc_t vc; 20 chdr_eob_t eob; 21 chdr_eov_t eov; 22 bit has_time; 23 chdr_timestamp_t timestamp; 24 } packet_info_t; 25 26 27 // Return 1 if the packet info is equivalent, 0 otherwise. 28 function automatic bit packet_info_equal(const ref packet_info_t a, b); 29 // If there's no time then the timestamp value doesn't matter, so make them 30 // the same for comparison. 31 if (!a.has_time) begin 32 packet_info_t a_copy = a; 33 a_copy.timestamp = b.timestamp; 34 return a_copy == b; 35 end 36 return a == b; 37 endfunction : packet_info_equal 38 39 40 class ChdrIfaceBfm #(CHDR_W = 64, ITEM_W = 32) extends ChdrBfm #(CHDR_W); 41 42 // Redefine the ChdrPacket_t and chdr_word_t data types from ChdrBfm due to 43 // a bug in Vivado 2019.1. 44 typedef ChdrPacket #(CHDR_W) ChdrPacket_t; 45 typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t; 46 typedef ChdrData #(CHDR_W, ITEM_W)::item_t item_t; 47 48 localparam int BYTES_PER_CHDR_W = CHDR_W/8; 49 localparam int BYTES_PER_ITEM_W = ITEM_W/8; 50 51 chdr_seq_num_t seq_num; // Sequence number 52 53 protected int max_payload_length; // Maximum number of payload bytes per packet 54 protected int ticks_per_word; // Timestamp increment per CHDR_W sized word 55 56 57 // Class constructor to create a new BFM instance. 58 // 59 // m_chdr: Interface for the master connection (BFM's CHDR output) 60 // s_chdr: Interface for the slave connection (BFM's CHDR input) 61 // 62 function new( 63 virtual AxiStreamIf #(CHDR_W).master m_chdr, 64 virtual AxiStreamIf #(CHDR_W).slave s_chdr, 65 input int max_payload_length = 2**$bits(chdr_length_t), 66 input int ticks_per_word = CHDR_W/ITEM_W 67 ); 68 super.new(m_chdr, s_chdr); 69 this.seq_num = 0; 70 71 assert (CHDR_W % ITEM_W == 0) else begin 72 $fatal(1, "ChdrIfaceBfm::new: CHDR_W must be a multiple of ITEM_W"); 73 end 74 75 set_max_payload_length(max_payload_length); 76 set_ticks_per_word(ticks_per_word); 77 endfunction : new 78 79 80 // Set the maximum payload size for packets. This value is used to split 81 // large send requests across multiple packets. 82 // 83 // max_length: Maximum payload length in bytes for each packet 84 // 85 function void set_max_payload_length(int max_payload_length); 86 assert (max_payload_length % BYTES_PER_CHDR_W == 0) else begin 87 $fatal(1, "ChdrIfaceBfm::set_max_payload_length: max_payload_length must be a multiple of CHDR_W in bytes"); 88 end 89 this.max_payload_length = max_payload_length; 90 endfunction 91 92 93 // Return the maximum payload size for packets. This value is used to split 94 // large send requests across multiple packets. 95 function int get_max_payload_length(); 96 return max_payload_length; 97 endfunction 98 99 100 // Set the timestamp ticks per CHDR_W sized word. 101 // 102 // ticks_per_word: Amount to increment the timestamp per CHDR_W sized word 103 // 104 function void set_ticks_per_word(int ticks_per_word); 105 this.ticks_per_word = ticks_per_word; 106 endfunction 107 108 109 // Return the timestamp ticks per CHDR_W sized word. 110 function int get_ticks_per_word(); 111 return ticks_per_word; 112 endfunction 113 114 115 // Send a CHDR data packet. 116 // 117 // data: Data words to insert into the CHDR packet. 118 // data_bytes: The number of data bytes in the CHDR packet. This 119 // is useful if the data is not a multiple of the 120 // chdr_word_t size. 121 // metadata: Metadata words to insert into the CHDR packet. Omit this 122 // argument (or set to an empty array) to not include 123 // metadata. 124 // pkt_info: Data structure containing packet header information. 125 // 126 task send ( 127 input chdr_word_t data[$], 128 input int data_bytes = -1, 129 input chdr_word_t metadata[$] = {}, 130 input packet_info_t pkt_info = 0 131 ); 132 ChdrPacket_t chdr_packet; 133 chdr_header_t chdr_header; 134 135 // Build packet 136 chdr_packet = new(); 137 chdr_header = '{ 138 vc : pkt_info.vc, 139 eob : pkt_info.eob, 140 eov : pkt_info.eov, 141 seq_num : seq_num++, 142 pkt_type : pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS, 143 dst_epid : dst_epid, 144 default : 0 145 }; 146 chdr_packet.write_raw(chdr_header, data, metadata, pkt_info.timestamp, data_bytes); 147 148 // Send the packet 149 put_chdr(chdr_packet); 150 endtask : send 151 152 153 // Send a CHDR data packet, filling the payload with items. 154 // 155 // items: Data items to insert into the CHDR packet. 156 // metadata: Metadata words to insert into the CHDR packet. Omit this 157 // argument (or set to an empty array) to not include metadata. 158 // pkt_info: Data structure containing packet header information. 159 // 160 task send_items ( 161 input item_t items[$], 162 input chdr_word_t metadata[$] = {}, 163 input packet_info_t pkt_info = 0 164 ); 165 chdr_word_t data[$]; 166 data = ChdrData#(CHDR_W, ITEM_W)::item_to_chdr(items); 167 send(data, items.size()*BYTES_PER_ITEM_W, metadata, pkt_info); 168 endtask : send_items 169 170 171 // Send data as one or more CHDR data packets. The input data and metadata 172 // is automatically broken into max_payload_length'd packets. The 173 // timestamp, if present, is set for the first packet and updated for 174 // subsequent packets. The EOB and EOV are only applied to the last packet. 175 // 176 // data: Data words to insert into the CHDR packet. 177 // data_bytes: The number of data bytes in the CHDR packet. This 178 // is useful if the data is not a multiple of the 179 // chdr_word_t size. 180 // metadata: Metadata words to insert into the CHDR packet. Omit this 181 // argument (or set to an empty array) to not include 182 // metadata. 183 // pkt_info: Data structure containing packet header information. 184 // 185 task send_packets ( 186 input chdr_word_t data[$], 187 input int data_bytes = -1, 188 input chdr_word_t metadata[$] = {}, 189 input packet_info_t pkt_info = 0 190 ); 191 ChdrPacket_t chdr_packet; 192 chdr_header_t chdr_header; 193 chdr_pkt_type_t pkt_type; 194 chdr_word_t timestamp; 195 int num_pkts; 196 int payload_length; 197 int first_dword, last_dword; 198 int first_mword, last_mword; 199 bit eob, eov; 200 chdr_word_t temp_data[$]; 201 chdr_word_t temp_mdata[$]; 202 203 num_pkts = $ceil(real'(data.size()*BYTES_PER_CHDR_W) / max_payload_length); 204 pkt_type = pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS; 205 timestamp = pkt_info.timestamp; 206 207 if (data_bytes < 0) data_bytes = data.size() * BYTES_PER_CHDR_W; 208 209 // Make sure there's not too much metadata for this number of packets 210 assert(metadata.size() <= num_pkts * (2**$bits(chdr_num_mdata_t)-1)) else 211 $fatal(1, "ChdrIfaceBfm::send: Too much metadata for this send request"); 212 213 // Send the data, one packet at a time. 214 for (int i = 0; i < num_pkts; i++) begin 215 chdr_packet = new(); 216 217 // Figure out which data chunk to send next 218 if (i == num_pkts-1) begin 219 // The last packet, which may or may not be full-sized 220 eob = pkt_info.eob; 221 eov = pkt_info.eov; 222 payload_length = data_bytes - (num_pkts-1) * max_payload_length; 223 first_dword = i*max_payload_length/BYTES_PER_CHDR_W; 224 last_dword = data.size()-1; 225 first_mword = i*(2**$bits(chdr_num_mdata_t)-1); 226 last_mword = metadata.size()-1; 227 end else begin 228 // A full-sized packet, not the last 229 eob = 1'b0; 230 eov = 1'b0; 231 payload_length = max_payload_length; 232 first_dword = (i+0)*max_payload_length / BYTES_PER_CHDR_W; 233 last_dword = (i+1)*max_payload_length / BYTES_PER_CHDR_W - 1; 234 first_mword = (i+0)*(2**$bits(chdr_num_mdata_t)-1); 235 last_mword = (i+1)*(2**$bits(chdr_num_mdata_t)-1) - 1; 236 last_mword = last_mword > metadata.size() ? metadata.size() : last_mword; 237 end 238 239 // Build the packet 240 chdr_header = '{ 241 vc : pkt_info.vc, 242 eob : eob, 243 eov : eov, 244 seq_num : seq_num++, 245 pkt_type : pkt_type, 246 dst_epid : dst_epid, 247 default : 0 248 }; 249 250 // Copy region of data and metadata to be sent in next packet 251 temp_data = data[first_dword : last_dword]; 252 if (first_mword < metadata.size()) temp_mdata = metadata[first_mword : last_mword]; 253 else temp_mdata = {}; 254 255 // Build the packet 256 chdr_packet.write_raw( 257 chdr_header, 258 temp_data, 259 temp_mdata, 260 timestamp, 261 payload_length 262 ); 263 264 // Send the packet 265 put_chdr(chdr_packet); 266 267 // Update timestamp for next packet (in case this is not the last) 268 timestamp += max_payload_length/BYTES_PER_CHDR_W * ticks_per_word; 269 end 270 endtask : send_packets 271 272 273 // Send one or more CHDR data packets, filling the payload with items. The 274 // input data and metadata is automatically broken into 275 // max_payload_length'd packets. The timestamp, if present, is set for the 276 // first packet and updated for subsequent packets. The EOB and EOV are 277 // only applied to the last packet. 278 // 279 // items: Data items to insert into the payload of the CHDR packets. 280 // metadata: Metadata words to insert into the CHDR packet. Omit this 281 // argument (or set to an empty array) to not include metadata. 282 // pkt_info: Data structure containing packet header information. 283 // 284 task send_packets_items ( 285 input item_t items[$], 286 input chdr_word_t metadata[$] = {}, 287 input packet_info_t pkt_info = 0 288 ); 289 chdr_word_t data[$]; 290 data = ChdrData#(CHDR_W, ITEM_W)::item_to_chdr(items); 291 send_packets(data, items.size()*BYTES_PER_ITEM_W, metadata, pkt_info); 292 endtask : send_packets_items 293 294 295 // Receive a CHDR data packet and extract its contents. 296 // 297 // data: Data words from the received CHDR packet. 298 // data_bytes: The number of data bytes in the CHDR packet. This 299 // is useful if the data is not a multiple of the 300 // chdr_word_t size. 301 // metadata: Metadata words from the received CHDR packet. This 302 // will be an empty array if there was no metadata. 303 // pkt_info: Data structure to receive packet header information. 304 // 305 task recv_adv ( 306 output chdr_word_t data[$], 307 output int data_bytes, 308 output chdr_word_t metadata[$], 309 output packet_info_t pkt_info 310 ); 311 ChdrPacket_t chdr_packet; 312 get_chdr(chdr_packet); 313 314 data = chdr_packet.data; 315 data_bytes = chdr_packet.data_bytes(); 316 metadata = chdr_packet.metadata; 317 pkt_info.timestamp = chdr_packet.timestamp; 318 pkt_info.vc = chdr_packet.header.vc; 319 pkt_info.eob = chdr_packet.header.eob; 320 pkt_info.eov = chdr_packet.header.eov; 321 pkt_info.has_time = chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS ? 1 : 0; 322 endtask : recv_adv 323 324 325 // Receive a CHDR data packet and extract its contents, putting the payload 326 // into a queue of items. 327 // 328 // items: Items extracted from the payload of the received packet. 329 // metadata: Metadata words from the received CHDR packet. This will be 330 // an empty array if there was no metadata. 331 // pkt_info: Data structure to receive packet header information. 332 // 333 task recv_items_adv ( 334 output item_t items[$], 335 output chdr_word_t metadata[$], 336 output packet_info_t pkt_info 337 ); 338 chdr_word_t data[$]; 339 int data_bytes; 340 341 recv_adv(data, data_bytes, metadata, pkt_info); 342 items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(data, data_bytes); 343 assert (data_bytes % BYTES_PER_ITEM_W == 0) else begin 344 $error({"ChdrIfaceBfm::recv_items_adv: ", 345 "Received data was not a multiple of items"}); 346 end 347 endtask : recv_items_adv 348 349 350 // Receive a CHDR data packet and extract the data. Any metadata or 351 // timestamp, if present, are discarded. 352 // 353 // data: Data words from the received CHDR packet. 354 // data_bytes: The number of data bytes in the CHDR packet. This 355 // is useful if the data is not a multiple of the 356 // chdr_word_t size. 357 // 358 task recv(output chdr_word_t data[$], output int data_bytes); 359 ChdrPacket_t chdr_packet; 360 get_chdr(chdr_packet); 361 data = chdr_packet.data; 362 data_bytes = chdr_packet.data_bytes(); 363 endtask : recv 364 365 366 // Receive a CHDR data packet and extract its payload into a queue of 367 // items. Any metadata or timestamp, if present, are discarded. 368 // 369 // items: Data items extracted from payload of the received CHDR packet. 370 // 371 task recv_items( 372 output item_t items[$] 373 ); 374 chdr_word_t data[$]; 375 int data_bytes; 376 377 recv(data, data_bytes); 378 items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(data, data_bytes); 379 assert (data_bytes % BYTES_PER_ITEM_W == 0) else begin 380 $error({"ChdrIfaceBfm::recv_items: ", 381 "Received data was not a multiple of items"}); 382 end 383 endtask : recv_items 384 385 386 // Receive one ore more CHDR data packets and extract their contents, 387 // putting the payload into a queue of items. Any metadata or timestamp, if 388 // present, are discarded. 389 // 390 // items: Items extracted from the payload of the received packets. 391 // num_items: (Optional) Minimum number of items to receive. This must be 392 // provided, unless eob or eov are set. Defaults to -1, which 393 // means that the number of items is not limited. 394 // eob: (Optional) Receive up until the next End of Burst (EOB). 395 // Default value is 1, so an entire burst is received. 396 // eov: (Optional) Receive up until the next End of Vector (EOV). 397 // 398 task recv_packets_items( 399 output item_t items[$], 400 input int num_items = -1, // Receive a full burst by default 401 input bit eob = 1, 402 input bit eov = 0 403 ); 404 chdr_word_t metadata[$]; 405 packet_info_t pkt_info; 406 recv_packets_items_adv(items, metadata, pkt_info, num_items, eob, eov); 407 endtask : recv_packets_items 408 409 410 // Receive one or more CHDR data packets and extract their contents, 411 // putting the payload into a queue of items and the metadata into a queue 412 // of CHDR words. 413 // 414 // items: Items extracted from the payload of the received packets. 415 // metadata: Metadata words from the received CHDR packets. This will be 416 // an empty array if there was no metadata. 417 // pkt_info: Data structure to receive packet information. The 418 // timestamp, if present, will correspond to the time of the 419 // first sample, whereas vc/eob/eov will correspond to the 420 // state of the last packet. 421 // num_items: (Optional) Minimum number of items to receive. This must be 422 // provided, unless eob or eov are set. Defaults to -1, which 423 // means that the number of items is not limited. 424 // eob: (Optional) Receive up until the next End of Burst (EOB). 425 // Default value is 1, so an entire burst is received. 426 // eov: (Optional) Receive up until the next End of Vector (EOV). 427 // 428 task recv_packets_items_adv( 429 output item_t items[$], 430 output chdr_word_t metadata[$], 431 output packet_info_t pkt_info, 432 input int num_items = -1, // Receive a full burst by default 433 input bit eob = 1, 434 input bit eov = 0 435 ); 436 chdr_word_t pkt_data[$]; 437 chdr_word_t pkt_metadata[$]; 438 int pkt_data_bytes; 439 int item_count; 440 packet_info_t time_info; 441 item_t new_items[$]; 442 443 if (num_items < 0) begin 444 assert (eob || eov) else begin 445 $fatal(1, {"ChdrIfaceBfm::recv_packets_items_adv: ", 446 "eob or eov must be set when num_items is not limited"}); 447 end 448 num_items = 32'h7FFF_FFFF; 449 end 450 451 time_info = 0; 452 items = {}; 453 metadata = {}; 454 while (items.size() < num_items) begin 455 // Receive the next packet 456 recv_adv(pkt_data, pkt_data_bytes, pkt_metadata, pkt_info); 457 458 if (items.size() == 0) begin 459 // First packet, so grab the timestamp if it exists 460 if (pkt_info.has_time) time_info = pkt_info; 461 end 462 463 // Enqueue the data 464 new_items = ChdrData#(CHDR_W, ITEM_W)::chdr_to_item(pkt_data, pkt_data_bytes); 465 items = {items, new_items}; 466 assert (pkt_data_bytes % BYTES_PER_ITEM_W == 0) else begin 467 $error({"ChdrIfaceBfm::recv_packets_items_adv: ", 468 "Received data was not a multiple of items"}); 469 end 470 471 // Enqueue the metadata 472 metadata = {metadata, pkt_metadata}; 473 474 if ((eob && pkt_info.eob) || (eov && pkt_info.eov)) break; 475 end 476 477 // Restore timestamp from the first packet 478 pkt_info.has_time = time_info.has_time; 479 pkt_info.timestamp = time_info.timestamp; 480 endtask : recv_packets_items_adv 481 482 endclass : ChdrIfaceBfm 483 484 485endpackage : PkgChdrIfaceBfm 486