1// 2// Copyright 2020 Ettus Research, A National Instruments Brand 3// 4// SPDX-License-Identifier: LGPL-3.0-or-later 5// 6// Module: PkgAxiStreamBfm 7// 8// Description: Package for a bi-directional AXI Stream bus functional model 9// (BFM). This consists of the AxiStreamPacket and AxiStreamBfm classes. 10// It's based on the AxiStreamIf in lib/axi4s_svPkgAXI4S.sv 11// 12 13 14//----------------------------------------------------------------------------- 15// AXI-Stream BFM Package 16//----------------------------------------------------------------------------- 17 18package PkgAxiStreamBfm; 19 20 //--------------------------------------------------------------------------- 21 // Raw packets - packet of just bytes 22 //--------------------------------------------------------------------------- 23 24 // Ethernet in particular defies normal word boundaries, so underneath treating 25 // it as the most fundamental quantity - Bytes. 26 typedef byte raw_pkt_t[$]; // Byte Queue 27 28 // Push a packet with random data onto to the AXI Stream bus 29 // Args: 30 // - num_bytes: number of random bytes to add to the raw packet. 31 // - pkt: packet with rand data 32 task automatic get_rand_raw_pkt ( 33 input int num_bytes, 34 output raw_pkt_t pkt); 35 begin 36 repeat(num_bytes) begin 37 pkt.push_back($urandom); 38 end 39 end 40 endtask 41 42 // Push a packet with a ramp on to the AXI Stream bus 43 // Args: 44 // - num_samps: Packet size in bytes *8 45 // - ramp_start: Start value for the ramp 46 // - ramp_inc: Increment per clock cycle 47 task automatic get_ramp_raw_pkt ( 48 input int num_samps, 49 input logic [63:0] ramp_start, 50 input logic [63:0] ramp_inc, 51 input int SWIDTH=64, 52 output raw_pkt_t pkt); 53 begin 54 logic[63:0] word; 55 automatic integer counter = 0; 56 repeat(num_samps) begin 57 word = ramp_start+(counter*ramp_inc); 58 for (int i=0; i < SWIDTH ; i+=8) begin 59 pkt.push_back(word[i +: 8]); 60 end 61 counter = counter + 1; 62 end 63 end 64 endtask 65 66 // Comparison Functions 67 function automatic bit raw_pkt_compare(input raw_pkt_t a, input raw_pkt_t b); 68 69 bit queue_match; 70 queue_match = 1; 71 // check each element of the queue and clear queue_match if they don't match. 72 // workaround for vivado bug - could be a==b 73 foreach(a[i]) queue_match = queue_match && a[i] == b[i]; 74 return ((a.size() == b.size()) && queue_match); 75 76 endfunction 77 78 //--------------------------------------------------------------------------- 79 // AXI Stream Packet Class 80 //--------------------------------------------------------------------------- 81 82 class AxiStreamPacket #(DATA_WIDTH = 64, USER_WIDTH = 1); 83 84 //------------------ 85 // Type Definitions 86 //------------------ 87 88 typedef logic [DATA_WIDTH-1:0] data_t; // Single bus TDATA word 89 typedef logic [DATA_WIDTH/8-1:0] keep_t; // Single TKEEP word 90 typedef logic [USER_WIDTH-1:0] user_t; // Single TUSER word 91 92 typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket_t; 93 94 bit verbose=0; 95 //------------ 96 // Properties 97 //------------ 98 99 data_t data[$]; 100 user_t user[$]; 101 keep_t keep[$]; 102 103 104 //--------- 105 // Methods 106 //--------- 107 108 // Return a handle to a copy of this transaction 109 function AxisPacket_t copy(); 110 AxisPacket_t temp; 111 temp = new(); 112 temp.data = this.data; 113 temp.user = this.user; 114 temp.keep = this.keep; 115 return temp; 116 endfunction 117 118 119 // Delete the contents of the current packet 120 function void empty(); 121 data = {}; 122 user = {}; 123 keep = {}; 124 endfunction; 125 126 // Delete a word from the current packet 127 function void delete(int i); 128 data.delete(i); 129 user.delete(i); 130 keep.delete(i); 131 endfunction; 132 133 // Return true if this packet equals that of the argument 134 virtual function bit equal(AxisPacket_t packet); 135 // These variables are needed to workaround Vivado queue support issues 136 data_t data_a, data_b; 137 user_t user_a, user_b; 138 keep_t keep_a, keep_b; 139 140 if (data.size() != packet.data.size()) return 0; 141 foreach (data[i]) begin 142 data_a = data[i]; 143 data_b = packet.data[i]; 144 if (data_a !== data_b) begin 145 if (verbose) $display("AxisPacket data mismatch a[%2d]=%X b[%2d]=%X",i,data_a,i,data_b); 146 return 0; 147 end 148 end 149 150 if (user.size() != packet.user.size()) return 0; 151 foreach (data[i]) begin 152 user_a = user[i]; 153 user_b = packet.user[i]; 154 if (user_a !== user_b) begin 155 if (verbose) $display("AxisPacket user mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b); 156 return 0; 157 end 158 end 159 160 if (keep.size() != packet.keep.size()) return 0; 161 foreach (keep[i]) begin 162 keep_a = keep[i]; 163 keep_b = packet.keep[i]; 164 if (keep_a !== keep_b) begin 165 if (verbose) $display("AxisPacket keep mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b); 166 return 0; 167 end 168 end 169 170 return 1; 171 endfunction : equal 172 173 174 // Format the contents of the packet into a string 175 function string sprint(); 176 string str = ""; 177 string data_str = ""; 178 if (data.size() == user.size() && data.size() == keep.size()) begin 179 str = { str, "data, user, keep:\n" }; 180 foreach (data[i]) begin 181 data_str = ""; 182 if (DATA_WIDTH > 64) begin 183 for (int b=0; b < DATA_WIDTH; b +=64) begin 184 data_str = { data_str, $sformatf("%3d: %X",b,data[i][b+:64])}; 185 if (b+64 < DATA_WIDTH) begin 186 data_str = { data_str, $sformatf("\n ")}; 187 end 188 end 189 end else begin 190 data_str = { data_str, $sformatf("%X",data[i]) }; 191 end 192 str = { str, $sformatf("%5d> %s %X %X \n", i, data_str, user[i], keep[i]) }; 193 end 194 end else begin 195 str = { str, "data:\n" }; 196 foreach (data[i]) begin 197 str = { str, $sformatf("%5d> %X\n", i, data[i]) }; 198 end 199 str = { str, "user:\n" }; 200 foreach (user[i]) begin 201 str = { str, $sformatf("%5d> %X\n", i, user[i]) }; 202 end 203 str = { str, "keep:\n" }; 204 foreach (keep[i]) begin 205 str = { str, $sformatf("%5d> %X\n", i, keep[i]) }; 206 end 207 end 208 return str; 209 endfunction : sprint 210 211 212 // Print the contents of the packet 213 function void print(); 214 $display(sprint()); 215 endfunction : print 216 217 // Add an array of bytes (little endian) 218 function void push_bytes(raw_pkt_t raw, input user_t user = '0); 219 data_t word; 220 keep_t my_keep; 221 while (raw.size() > 0) begin 222 // fill tkeep 223 // SIZE = TKEEP / 0 = 0000, 1 = 0001, 2 = 0011, etc 224 my_keep = '1; 225 if (raw.size <= DATA_WIDTH/8) begin 226 foreach (my_keep[i]) my_keep[i] = i < (raw.size); 227 end 228 // fill the word with raw data from bottom up 229 word = '0; 230 for (int i = 0; i < DATA_WIDTH/8 ; i++) begin 231 if (my_keep[i]) word[i*8 +: 8] = raw.pop_front(); 232 end 233 this.data.push_back(word); 234 this.keep.push_back(my_keep); 235 this.user.push_back(user); 236 end 237 endfunction 238 239 // Dump data contents as an array of bytes. (little endian) 240 function raw_pkt_t dump_bytes(); 241 data_t word; 242 keep_t my_keep; 243 raw_pkt_t raw; 244 assert (data.size == keep.size) else 245 $fatal("data and keep have different sizes!"); 246 foreach (data[i]) begin 247 my_keep = this.keep[i]; 248 word = this.data[i]; 249 for (int j = 0; j < DATA_WIDTH/8 ; j++) begin 250 if (my_keep[j]) raw.push_back(word[j*8 +: 8]); 251 end; 252 end 253 return raw; 254 endfunction 255 256 endclass : AxiStreamPacket; 257 258 259 260 //--------------------------------------------------------------------------- 261 // AXI Stream BFM Class 262 //--------------------------------------------------------------------------- 263 264 class AxiStreamBfm #( 265 int DATA_WIDTH = 64, 266 int USER_WIDTH = 1, 267 int MAX_PACKET_BYTES = 0, 268 bit TDATA = 1, 269 bit TUSER = 1, 270 bit TKEEP = 1, 271 bit TLAST = 1 272 ); 273 274 //------------------ 275 // Type Definitions 276 //------------------ 277 278 typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket_t; 279 typedef AxisPacket_t::data_t data_t; 280 typedef AxisPacket_t::user_t user_t; 281 typedef AxisPacket_t::keep_t keep_t; 282 283 284 //------------ 285 // Properties 286 //------------ 287 288 // Default stall probability, as a percentage (0-100). 289 local const int DEF_STALL_PROB = 38; 290 291 // Default values to use for idle bus cycles 292 local const AxisPacket_t::data_t IDLE_DATA = {DATA_WIDTH{1'bX}}; 293 local const AxisPacket_t::user_t IDLE_USER = {(USER_WIDTH > 1 ? USER_WIDTH : 1){1'bX}}; 294 local const AxisPacket_t::keep_t IDLE_KEEP = {(DATA_WIDTH/8){1'bX}}; 295 296 // Virtual interfaces for master and slave connections to DUT 297 local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, 298 TDATA,TUSER,TKEEP,TLAST).master master; 299 local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, 300 TDATA,TUSER,TKEEP,TLAST).slave slave; 301 // NOTE: We should not need these flags if Vivado would be OK with null check 302 // without throwing unnecessary null-ptr deref exceptions. 303 local bit master_en; 304 local bit slave_en; 305 306 bit slave_tready_init = 0; 307 // Queues to store the bus transactions 308 mailbox #(AxisPacket_t) tx_packets; 309 mailbox #(AxisPacket_t) rx_packets; 310 311 // Properties for the stall behavior of the BFM 312 protected int master_stall_prob = DEF_STALL_PROB; 313 protected int slave_stall_prob = DEF_STALL_PROB; 314 315 // Number of clocks betwen packets 316 int inter_packet_gap = 0; 317 318 //--------- 319 // Methods 320 //--------- 321 322 // Returns 1 if the packets have the same contents, otherwise returns 0. 323 function bit packets_equal(AxisPacket_t a, AxisPacket_t b); 324 return a.equal(b); 325 endfunction : packets_equal 326 327 328 // Class constructor. This must be given an interface for the master 329 // connection and an interface for the slave connection. 330 function new( 331 virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, 332 TDATA,TUSER,TKEEP,TLAST).master master, 333 virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, 334 TDATA,TUSER,TKEEP,TLAST).slave slave 335 ); 336 this.master_en = (master != null); 337 this.slave_en = (slave != null); 338 this.master = master; 339 this.slave = slave; 340 tx_packets = new; 341 rx_packets = new; 342 endfunction : new 343 344 345 // Queue the provided packet for transmission 346 task put(AxisPacket_t packet); 347 assert (master_en) else $fatal(1, "Cannot use TX operations for a null master"); 348 tx_packets.put(packet); 349 endtask : put 350 351 352 // Attempt to queue the provided packet for transmission. Return 1 if 353 // successful, return 0 if the queue is full. 354 function bit try_put(AxisPacket_t packet); 355 assert (master_en) else $fatal(1, "Cannot use TX operations for a null master"); 356 return tx_packets.try_put(packet); 357 endfunction : try_put 358 359 360 // Get the next packet when it becomes available (waits if necessary) 361 task get(output AxisPacket_t packet); 362 assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); 363 rx_packets.get(packet); 364 endtask : get 365 366 367 // Get the next packet if there's one available and return 1. Return 0 if 368 // there's no packet available. 369 function bit try_get(output AxisPacket_t packet); 370 assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); 371 return rx_packets.try_get(packet); 372 endfunction : try_get 373 374 375 // Get the next packet when it becomes available (wait if necessary), but 376 // don't remove it from the receive queue. 377 task peek(output AxisPacket_t packet); 378 assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); 379 rx_packets.peek(packet); 380 endtask : peek 381 382 383 // Get the next packet if there's one available and return 1, but don't 384 // remove it from the receive queue. Return 0 if there's no packet 385 // available. 386 function bit try_peek(output AxisPacket_t packet); 387 assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); 388 return rx_packets.try_peek(packet); 389 endfunction : try_peek 390 391 392 // Return the number of packets available in the receive queue 393 function int num_received(); 394 assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); 395 return rx_packets.num(); 396 endfunction 397 398 399 // Wait until num packets have started transmission (i.e., until num 400 // packets have been dequeued). Set num = -1 to wait until all currently 401 // queued packets have started transmission. 402 task wait_send(int num = -1); 403 int end_num; 404 assert (master_en) else $fatal(1, "Cannot use TX operations for a null master"); 405 406 if (num == -1) end_num = 0; 407 else begin 408 end_num = tx_packets.num() - num; 409 assert(end_num >= 0) else begin 410 $fatal(1, "Not enough packets queued to wait for %0d packets", num); 411 end 412 end 413 while(tx_packets.num() > end_num) @(posedge master.clk); 414 endtask : wait_send 415 416 417 // Wait until num packets have completed transmission. Set num = -1 to wait 418 // for all currently queued packets to complete transmission. 419 task wait_complete(int num = -1); 420 int end_num; 421 assert (master_en) else $fatal(1, "Cannot use TX operations for a null master"); 422 423 if (num == -1) num = tx_packets.num(); 424 else begin 425 assert(num <= tx_packets.num()) else begin 426 $fatal(1, "Not enough packets queued to wait for %0d packets", num); 427 end 428 end 429 430 repeat (num) begin 431 @(posedge master.tlast); // Wait for last word 432 do begin // Wait until the last word is accepted 433 @(posedge master.clk); 434 end while(master.tready != 1); 435 end 436 endtask : wait_complete 437 438 439 // Set the probability (as a percentage, 0 to 100) of the master interface 440 // stalling due to lack of data to send. 441 function void set_master_stall_prob(int stall_probability = DEF_STALL_PROB); 442 assert(stall_probability >= 0 && stall_probability <= 100) else begin 443 $fatal(1, "Invalid master stall_probability value"); 444 end 445 master_stall_prob = stall_probability; 446 endfunction 447 448 449 // Set the probability (as a percentage, 0 to 100) of the slave interface 450 // stalling due to lack of buffer space. 451 function void set_slave_stall_prob(int stall_probability = DEF_STALL_PROB); 452 assert(stall_probability >= 0 && stall_probability <= 100) else begin 453 $fatal(1, "Invalid slave stall_probability value"); 454 end 455 slave_stall_prob = stall_probability; 456 endfunction 457 458 459 // Get the probability (as a percentage, 0 to 100) of the master interface 460 // stalling due to lack of data to send. 461 function int get_master_stall_prob(int stall_probability = DEF_STALL_PROB); 462 return master_stall_prob; 463 endfunction 464 465 466 // Get the probability (as a percentage, 0 to 100) of the slave interface 467 // stalling due to lack of buffer space. 468 function int get_slave_stall_prob(int stall_probability = DEF_STALL_PROB); 469 return slave_stall_prob; 470 endfunction 471 472 473 // Create separate processes for driving the master and slave interfaces 474 task run(); 475 fork 476 if (master_en) master_body(); 477 if (slave_en) slave_body(); 478 join_none 479 endtask 480 481 482 //---------------- 483 // Master Process 484 //---------------- 485 486 local task master_body(); 487 AxisPacket_t packet; 488 489 master.tvalid <= 0; 490 master.tdata <= IDLE_DATA; 491 master.tuser <= IDLE_USER; 492 master.tkeep <= IDLE_KEEP; 493 master.tlast <= 0; 494 495 forever begin 496 repeat(inter_packet_gap) begin 497 @(posedge master.clk); 498 if (master.rst) continue; 499 end 500 501 if (tx_packets.try_get(packet)) begin 502 foreach (packet.data[i]) begin 503 // Randomly deassert tvalid for next word and stall 504 if ($urandom_range(99) < master_stall_prob) begin 505 master.tvalid <= 0; 506 master.tdata <= IDLE_DATA; 507 master.tuser <= IDLE_USER; 508 master.tkeep <= IDLE_KEEP; 509 master.tlast <= 0; 510 do begin 511 @(posedge master.clk); 512 if (master.rst) break; 513 end while ($urandom_range(99) < master_stall_prob); 514 if (master.rst) break; 515 end 516 517 // Send the next word 518 master.tvalid <= 1; 519 master.tdata <= packet.data[i]; 520 master.tuser <= packet.user[i]; 521 master.tkeep <= packet.keep[i]; 522 if (i == packet.data.size()-1) master.tlast <= 1; 523 524 do begin 525 @(posedge master.clk); 526 if (master.rst) break; 527 end while (!master.tready); 528 end 529 master.tvalid <= 0; 530 master.tdata <= IDLE_DATA; 531 master.tuser <= IDLE_USER; 532 master.tkeep <= IDLE_KEEP; 533 master.tlast <= 0; 534 end else begin 535 @(posedge master.clk); 536 if (master.rst) continue; 537 end 538 end 539 endtask : master_body 540 541 542 //--------------- 543 // Slave Process 544 //--------------- 545 546 local task slave_body(); 547 AxisPacket_t packet = new(); 548 549 slave.tready <= slave_tready_init; 550 551 forever begin 552 @(posedge slave.clk); 553 if (slave.rst) continue; 554 555 if (slave.tvalid) begin 556 if (slave.tready) begin 557 packet.data.push_back(slave.tdata); 558 packet.user.push_back(slave.tuser); 559 packet.keep.push_back(slave.tkeep); 560 if (slave.tlast) begin 561 rx_packets.put(packet.copy()); 562 packet.data = {}; 563 packet.user = {}; 564 packet.keep = {}; 565 end 566 end 567 slave.tready <= $urandom_range(99) < slave_stall_prob ? 0 : 1; 568 end 569 end 570 endtask : slave_body 571 572 endclass : AxiStreamBfm 573 574 575endpackage : PkgAxiStreamBfm 576