aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/sim/rfnoc/PkgChdrIfaceBfm.sv
blob: a25f29d1847bb38d01a32f876f1b7678b8937b6e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
//
// 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;


  class ChdrIfaceBfm #(CHDR_W = 64, ITEM_W = 32) extends ChdrBfm #(CHDR_W);

    // Redefine the ChdrPacket and chdr_word_t data types from ChdrBfm due to a
    // bug in Vivado 2019.1.
    typedef ChdrPacket #(CHDR_W)                      ChdrPacket;
    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      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      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 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 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