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