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